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 {
private final LensQueryResult mLensQueryResultWithShoppingItent =
(new LensQueryResult.Builder()).withIsShoppyIntent(true).build();
private boolean mEnableLensWithSearchByImageText;
private boolean mIsLensIntentInProgress;
private @Nullable UkmRecorder.Bridge mUkmRecorderBridge;
private ContextMenuNativeDelegate mNativeDelegate;
private static final String LENS_SEARCH_MENU_ITEM_KEY = "searchWithGoogleLensMenuItem";
......@@ -774,6 +775,11 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator {
TrackerFactory.getTrackerForProfile(Profile.getLastUsedRegularProfile());
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() {
......@@ -834,6 +840,7 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator {
*/
protected void searchWithGoogleLens(
boolean requiresConfirmation, @Nullable LensQueryResult lensQueryResult) {
mIsLensIntentInProgress = true;
mNativeDelegate.retrieveImageForShare(ContextMenuImageFormat.PNG, (Uri imageUri) -> {
ShareHelper.shareImageWithGoogleLens(getWindow(), imageUri, mItemDelegate.isIncognito(),
mParams.getSrcUrl(), mParams.getTitleText(), mParams.getPageUrl(),
......
......@@ -224,6 +224,8 @@ public class ContextMenuHelper {
Log.i(TAG, "Created mCurrentContextMenu: " + mCurrentContextMenu);
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) {
menuCoordinator.displayMenuWithChip(mWindow, mWebContents, mCurrentContextMenuParams,
items, mCallback, mOnMenuShown, mOnMenuClosed, mChipDelegate);
......
......@@ -16,6 +16,8 @@ import org.chromium.base.ContextUtils;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
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.profiles.Profile;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
......@@ -28,6 +30,7 @@ import org.chromium.url.GURL;
/**
* This class provides utilities for intenting into Google Lens.
*/
// TODO(crbug/1157496): Consolidate param-checks into a single function.
public class LensUtils {
private static final String LENS_CONTRACT_URI = "googleapp://lens";
private static final String LENS_DIRECT_INTENT_CONTRACT_URI = "google://lens";
......@@ -47,6 +50,8 @@ public class LensUtils {
"minAgsaVersionNameForShopping";
private static final String MIN_AGSA_VERSION_DIRECT_INTENT_FEATURE_PARAM_NAME =
"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 =
"useSearchByImageText";
private static final String LENS_SHOPPING_ALLOWLIST_ENTRIES_FEATURE_PARAM_NAME =
......@@ -58,12 +63,15 @@ public class LensUtils {
private static final String SEND_ALT_PARAM_NAME = "sendAlt";
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_SDK_INTEGRATION_PARAM_NAME =
"useDirectIntentSdkIntegration";
private static final String DISABLE_ON_INCOGNITO_PARAM_NAME = "disableOnIncognito";
private static final String ORDER_SHARE_IMAGE_BEFORE_LENS_PARAM_NAME =
"orderShareImageBeforeLens";
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_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 String LENS_SHOPPING_FEATURE_FLAG_VARIANT_NAME = "lensShopVariation";
private static final String LENS_DEFAULT_SHOPPING_URL_PATTERNS =
......@@ -220,6 +228,32 @@ public class LensUtils {
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
* to limit to OS"s where image processing vulnerabilities can be retroactively
......@@ -342,6 +376,59 @@ public class LensUtils {
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) {
return ChromeFeatureList.isEnabled(ChromeFeatureList.CONTEXT_MENU_SEARCH_WITH_GOOGLE_LENS)
&& !(isIncognito
......@@ -379,6 +466,24 @@ public class LensUtils {
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
*/
......
......@@ -27,6 +27,8 @@ import org.chromium.base.PackageManagerUtils;
import org.chromium.base.StrictModeContext;
import org.chromium.base.metrics.RecordHistogram;
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.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
......@@ -134,6 +136,12 @@ public class ShareHelper extends org.chromium.components.browser_ui.share.ShareH
public static void shareImageWithGoogleLens(final WindowAndroid window, Uri imageUri,
boolean isIncognito, GURL srcUrl, String titleOrAltText, GURL pageUrl,
LensQueryResult lensQueryResult, boolean requiresConfirmation) {
if (LensUtils.useDirectIntentSdkIntegration(ContextUtils.getApplicationContext())) {
LensIntentParams intentParams = LensUtils.buildLensIntentParams(imageUri, isIncognito,
srcUrl.getValidSpecOrEmpty(), titleOrAltText, pageUrl.getValidSpecOrEmpty(),
requiresConfirmation);
LensController.getInstance().startLens(window, intentParams);
} else {
Intent shareIntent =
LensUtils.getShareWithGoogleLensIntent(ContextUtils.getApplicationContext(),
imageUri, isIncognito, SystemClock.elapsedRealtimeNanos(), srcUrl,
......@@ -143,12 +151,14 @@ public class ShareHelper extends org.chromium.components.browser_ui.share.ShareH
// 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.
// 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;
}
}
}
/**
* Set the icon and the title for the menu item used for direct share.
......
......@@ -29,6 +29,7 @@ import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
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.test.ChromeBrowserTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
......@@ -914,6 +915,36 @@ public class LensUtilsTest {
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) {
return TestThreadUtils.runOnUiThreadBlockingNoException(
() -> 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