Commit e86e1ae7 authored by Yu Su's avatar Yu Su Committed by Commit Bot

Refactor Lens chip code

- Decouple Lens Chip onClick from Lens Shopping Menu item selection.
- Switch to use #queryImage in LensAsyncManager. Will deprecate #classifyImage in a follow up cl.
- Support using the Lens intent type in Lens Prime API callback when creating Lens deeplink intent.

Change-Id: I541d080e4ec269fee4577153183037f15e78f576
Bug: 1099982, b/170126006
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2463455
Commit-Queue: Yu Su <yusuyoutube@google.com>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Reviewed-by: default avatarSinan Sahin <sinansahin@google.com>
Cr-Commit-Position: refs/heads/master@{#819876}
parent 11aaf149
......@@ -38,6 +38,7 @@ import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
import org.chromium.chrome.browser.firstrun.FirstRunStatus;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.gsa.GSAState;
import org.chromium.chrome.browser.lens.LensQueryResult;
import org.chromium.chrome.browser.locale.LocaleManager;
import org.chromium.chrome.browser.metrics.UkmRecorder;
import org.chromium.chrome.browser.performance_hints.PerformanceHintsObserver;
......@@ -85,6 +86,9 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator {
private final Supplier<ShareDelegate> mShareDelegateSupplier;
private final ExternalAuthUtils mExternalAuthUtils;
private final ContextMenuParams mParams;
// A predefined LensQueryResult used for Lens Shopping context menu item selection.
private final LensQueryResult mLensQueryResultWithShoppingItent =
(new LensQueryResult.Builder()).withIsShoppyIntent(true).build();
private boolean mEnableLensWithSearchByImageText;
private @Nullable UkmRecorder.Bridge mUkmRecorderBridge;
private ContextMenuNativeDelegate mNativeDelegate;
......@@ -584,6 +588,11 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator {
return mItemDelegate.isIncognito();
}
@Override
public String getPageTitle() {
return mItemDelegate.getPageTitle();
}
@Override
public boolean onItemSelected(int itemId) {
if (itemId == R.id.contextmenu_open_in_new_tab) {
......@@ -680,35 +689,33 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator {
ShareHelper.shareWithLastUsedComponent(shareParams);
} else if (itemId == R.id.contextmenu_search_with_google_lens) {
recordContextMenuSelection(ContextMenuUma.Action.SEARCH_WITH_GOOGLE_LENS);
searchWithGoogleLens(mItemDelegate.isIncognito());
searchWithGoogleLens(/*requiresConfirmation=*/false, /*lensQueryResult=*/null);
SharedPreferencesManager prefManager = SharedPreferencesManager.getInstance();
prefManager.writeBoolean(
ChromePreferenceKeys.CONTEXT_MENU_SEARCH_WITH_GOOGLE_LENS_CLICKED, true);
} else if (itemId == R.id.contextmenu_search_by_image) {
if (mEnableLensWithSearchByImageText) {
recordContextMenuSelection(ContextMenuUma.Action.SEARCH_WITH_GOOGLE_LENS);
searchWithGoogleLens(mItemDelegate.isIncognito());
searchWithGoogleLens(/*requiresConfirmation=*/false, /*lensQueryResult=*/null);
} else {
recordContextMenuSelection(ContextMenuUma.Action.SEARCH_BY_IMAGE);
mNativeDelegate.searchForImage();
}
} else if (itemId == R.id.contextmenu_shop_similar_products) {
recordContextMenuSelection(ContextMenuUma.Action.SHOP_SIMILAR_PRODUCTS);
shopWithGoogleLens(mItemDelegate.isIncognito(),
/*requiresConfirmation=*/true);
searchWithGoogleLens(/*requiresConfirmation=*/true, mLensQueryResultWithShoppingItent);
SharedPreferencesManager prefManager = SharedPreferencesManager.getInstance();
prefManager.writeBoolean(
ChromePreferenceKeys.CONTEXT_MENU_SHOP_SIMILAR_PRODUCTS_CLICKED, true);
} else if (itemId == R.id.contextmenu_shop_image_with_google_lens) {
recordContextMenuSelection(ContextMenuUma.Action.SHOP_IMAGE_WITH_GOOGLE_LENS);
shopWithGoogleLens(mItemDelegate.isIncognito(), /*requiresConfirmation=*/false);
searchWithGoogleLens(/*requiresConfirmation=*/false, mLensQueryResultWithShoppingItent);
SharedPreferencesManager prefManager = SharedPreferencesManager.getInstance();
prefManager.writeBoolean(
ChromePreferenceKeys.CONTEXT_MENU_SHOP_IMAGE_WITH_GOOGLE_LENS_CLICKED, true);
} else if (itemId == R.id.contextmenu_search_similar_products) {
recordContextMenuSelection(ContextMenuUma.Action.SEARCH_SIMILAR_PRODUCTS);
shopWithGoogleLens(mItemDelegate.isIncognito(),
/*requiresConfirmation=*/true);
searchWithGoogleLens(/*requiresConfirmation=*/true, mLensQueryResultWithShoppingItent);
SharedPreferencesManager prefManager = SharedPreferencesManager.getInstance();
prefManager.writeBoolean(
ChromePreferenceKeys.CONTEXT_MENU_SEARCH_SIMILAR_PRODUCTS_CLICKED, true);
......@@ -767,31 +774,6 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator {
ContextMenuImageFormat.ORIGINAL, mItemDelegate::onSaveImageToClipboard);
}
/**
* Search for the image by intenting to the lens app with the image data attached.
* @param isIncognito Whether the image to search came from an incognito context.
*/
private void searchWithGoogleLens(boolean isIncognito) {
mNativeDelegate.retrieveImageForShare(ContextMenuImageFormat.PNG, (Uri imageUri) -> {
ShareHelper.shareImageWithGoogleLens(getWindow(), imageUri, isIncognito,
mParams.getSrcUrl(), mParams.getTitleText(),
/* isShoppyImage*/ false, /* requiresConfirmation*/ false);
});
}
/**
* Search for the image by intenting to the lens app with the image data attached.
* @param isIncognito Whether the image to search came from an incognito context.
* @param requiresConfirmation Whether the request requires an account dialog.
*/
private void shopWithGoogleLens(boolean isIncognito, boolean requiresConfirmation) {
mNativeDelegate.retrieveImageForShare(ContextMenuImageFormat.PNG, (Uri imageUri) -> {
ShareHelper.shareImageWithGoogleLens(getWindow(), imageUri, isIncognito,
mParams.getSrcUrl(), mParams.getTitleText(), /* isShoppyImage*/ true,
requiresConfirmation);
});
}
/**
* Share the image that triggered the current context menu.
* Package-private, allowing access only from the context menu item to ensure that
......@@ -826,6 +808,20 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator {
return TemplateUrlServiceFactory.get();
}
/**
* Search for the image by intenting to the lens app with the image data attached.
* @param requiresConfirmation Whether the request requires an account dialog.
* @param lensQueryResult A wrapper object which contains the results for the Lens image query.
*/
protected void searchWithGoogleLens(
boolean requiresConfirmation, @Nullable LensQueryResult lensQueryResult) {
mNativeDelegate.retrieveImageForShare(ContextMenuImageFormat.PNG, (Uri imageUri) -> {
ShareHelper.shareImageWithGoogleLens(getWindow(), imageUri, mItemDelegate.isIncognito(),
mParams.getSrcUrl(), mParams.getTitleText(), lensQueryResult,
requiresConfirmation);
});
}
/**
* Checks whether a url is empty or blank.
* @param url The url need to be checked.
......
......@@ -15,6 +15,8 @@ import org.chromium.base.annotations.NativeMethods;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.task.PostTask;
import org.chromium.chrome.browser.lens.LensController;
import org.chromium.chrome.browser.lens.LensQueryParams;
import org.chromium.chrome.browser.lens.LensQueryResult;
import org.chromium.chrome.browser.performance_hints.PerformanceHintsObserver;
import org.chromium.chrome.browser.share.LensUtils;
import org.chromium.components.embedder_support.contextmenu.ContextMenuParams;
......@@ -48,6 +50,7 @@ public class ContextMenuHelper {
private long mMenuShownTimeMs;
private boolean mSelectedItemBeforeDismiss;
private boolean mIsIncognito;
private String mPageTitle;
private ContextMenuHelper(long nativeContextMenuHelper, WebContents webContents) {
mNativeContextMenuHelper = nativeContextMenuHelper;
......@@ -106,6 +109,7 @@ public class ContextMenuHelper {
mCurrentPopulator = mPopulatorFactory.createContextMenuPopulator(
windowAndroid.getActivity().get(), params, mCurrentNativeDelegate);
mIsIncognito = mCurrentPopulator.isIncognito();
mPageTitle = mCurrentPopulator.getPageTitle();
mCurrentContextMenuParams = params;
mWindow = windowAndroid;
mCallback = (result) -> {
......@@ -148,9 +152,18 @@ public class ContextMenuHelper {
// latency impact.
if (LensUtils.enableShoppyImageMenuItem()) {
Callback<Uri> callback = (Uri uri) -> {
LensController.getInstance().classifyImage(uri,
(Boolean isShoppyImage)
-> displayRevampedContextMenu(topContentOffsetPx, isShoppyImage));
LensQueryParams lensQueryParams =
(new LensQueryParams.Builder())
.withImageUri(uri)
.withPageUrl(mCurrentContextMenuParams.getPageUrl())
.withImageTitleOrAltText(mCurrentContextMenuParams.getTitleText())
.build();
LensController.getInstance().queryImage(lensQueryParams,
(LensQueryResult lensQueryResult)
-> displayRevampedContextMenu(topContentOffsetPx,
(lensQueryResult.getIsShoppyIntent()
|| LensUtils.isLensShoppingIntentType(
lensQueryResult.getLensIntentType()))));
};
mCurrentNativeDelegate.retrieveImageForShare(ContextMenuImageFormat.ORIGINAL, callback);
} else {
......@@ -171,8 +184,8 @@ public class ContextMenuHelper {
mCurrentContextMenu = menuCoordinator;
if (LensUtils.enableImageChip(mIsIncognito)) {
LensAsyncManager lensAsyncManager =
new LensAsyncManager(mCurrentContextMenuParams, mCurrentNativeDelegate);
LensAsyncManager lensAsyncManager = new LensAsyncManager(mCurrentContextMenuParams,
mCurrentNativeDelegate, mWindow, mIsIncognito, mPageTitle);
menuCoordinator.displayMenuWithLensChip(mWindow, mWebContents,
mCurrentContextMenuParams, items, mCallback, mOnMenuShown, mOnMenuClosed,
lensAsyncManager);
......
......@@ -8,8 +8,13 @@ import android.net.Uri;
import org.chromium.base.Callback;
import org.chromium.chrome.browser.lens.LensController;
import org.chromium.chrome.browser.lens.LensQueryParams;
import org.chromium.chrome.browser.lens.LensQueryResult;
import org.chromium.chrome.browser.share.ShareHelper;
import org.chromium.components.embedder_support.contextmenu.ContextMenuParams;
import org.chromium.ui.base.WindowAndroid;
// TODO(b/170970926): Move LensAsyncManager to the private code repository.
/**
* Manage requests to the Lens SDK which may be asynchronous.
*/
......@@ -18,28 +23,58 @@ class LensAsyncManager {
private ContextMenuParams mParams;
private ContextMenuNativeDelegate mNativeDelegate;
private LensQueryResult mLastCompletedQueryResult;
private WindowAndroid mWindow;
private boolean mIsIncognito;
private String mPageTitle;
/**
* Construct a lens async manager.
* @param params Context menu params used to retrieve additional metadata.
* @param nativeDelegate {@link ContextMenuNativeDelegate} used to retrieve image bytes.
* @param window The current window.
* @param isIncognito Whether the current tab is in incognito mode.
*/
public LensAsyncManager(ContextMenuParams params, ContextMenuNativeDelegate nativeDelegate) {
public LensAsyncManager(ContextMenuParams params, ContextMenuNativeDelegate nativeDelegate,
WindowAndroid window, boolean isIncognito, String pageTitle) {
mParams = params;
mNativeDelegate = nativeDelegate;
mWindow = window;
mIsIncognito = isIncognito;
mPageTitle = pageTitle;
}
/**
* Make a lens classification call for the current render frame.
* @param replyCallback The function to callback with the classification.
* Make a Lens image query for the current render frame.
* @param replyCallback The function to callback with the query result.
*/
public void classifyImageAsync(Callback<Boolean> replyCallback) {
Callback<Uri> callback = (uri)
-> LensController.getInstance().classifyImage(uri, mParams.getPageUrl(),
mParams.getTitleText(), (isClassificationSuccessful) -> {
replyCallback.onResult(isClassificationSuccessful);
});
public void queryImageAsync(Callback<LensQueryResult> replyCallback) {
Callback<Uri> callback = (uri) -> {
LensQueryParams lensQueryParams =
(new LensQueryParams.Builder())
.withImageUri(uri)
.withPageUrl(mParams.getPageUrl())
.withImageTitleOrAltText(mParams.getTitleText())
.withPageTitle(mPageTitle)
.build();
LensController.getInstance().queryImage(lensQueryParams, (lensQueryResult) -> {
mLastCompletedQueryResult = lensQueryResult;
replyCallback.onResult(lensQueryResult);
});
};
// Must occur on UI thread.
mNativeDelegate.retrieveImageForShare(ContextMenuImageFormat.ORIGINAL, callback);
}
/**
* Search with Google Lens with the last completed image query result.
*/
public void searchWithGoogleLens() {
Callback<Uri> callback = (uri) -> {
ShareHelper.shareImageWithGoogleLens(mWindow, uri, mIsIncognito, mParams.getSrcUrl(),
mParams.getTitleText(), mLastCompletedQueryResult,
/* requiresConfirmation*/ false);
};
mNativeDelegate.retrieveImageForShare(ContextMenuImageFormat.PNG, callback);
}
}
\ No newline at end of file
......@@ -11,10 +11,13 @@ import android.view.LayoutInflater;
import android.view.View;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.lens.LensQueryResult;
import org.chromium.chrome.browser.share.LensUtils;
import org.chromium.ui.text.SpanApplier;
import org.chromium.ui.text.SpanApplier.SpanInfo;
import org.chromium.ui.widget.AnchoredPopupWindow;
......@@ -27,9 +30,9 @@ import java.lang.annotation.RetentionPolicy;
* A controller to handle chip construction and cross-app communication.
*/
class RevampedContextMenuChipController implements View.OnClickListener {
private boolean mFakeLensQueryResultForTesting;
private View mAnchorView;
private LensAsyncManager mLensAsyncManager;
private Runnable mChipClickedCallback;
private ChipView mChipView;
private AnchoredPopupWindow mPopupWindow;
private Context mContext;
......@@ -55,14 +58,13 @@ class RevampedContextMenuChipController implements View.OnClickListener {
* @param lensAsyncManager The object responsible for making Lens requests.
* @param chipClickedCallback The callback to fire after a user clicks a lens chip.
*/
RevampedContextMenuChipController(Context context, View anchorView,
LensAsyncManager lensAsyncManager, Runnable chipClickedCallback) {
RevampedContextMenuChipController(
Context context, View anchorView, LensAsyncManager lensAsyncManager) {
mContext = context;
mLensAsyncManager = lensAsyncManager;
mChipClickedCallback = chipClickedCallback;
mAnchorView = anchorView;
mLensAsyncManager.classifyImageAsync(
(isShoppingIntent) -> { handleImageClassification(isShoppingIntent); });
mLensAsyncManager.queryImageAsync(
(lensQueryResult) -> { handleImageClassification(lensQueryResult); });
}
/**
......@@ -101,9 +103,24 @@ class RevampedContextMenuChipController implements View.OnClickListener {
R.dimen.context_menu_chip_icon_size));
}
// This method should only be used in test files. It is not marked
// @VisibleForTesting to allow the Coordinator to reference it in its
// own testing methods.
void setFakeLensQueryResultForTesting() {
mFakeLensQueryResultForTesting = true;
}
@VisibleForTesting
void handleImageClassification(boolean isShoppingIntent) {
if (isShoppingIntent) {
void handleImageClassification(@Nullable LensQueryResult lensQueryResult) {
if (mFakeLensQueryResultForTesting) {
lensQueryResult = (new LensQueryResult.Builder())
.withIsShoppyIntent(true)
.withLensIntentType(LensUtils.getLensShoppingIntentType())
.build();
}
if (lensQueryResult != null && lensQueryResult.getIsShoppyIntent()
|| LensUtils.isLensShoppingIntentType(lensQueryResult.getLensIntentType())) {
showChip(mAnchorView);
};
}
......@@ -112,7 +129,7 @@ class RevampedContextMenuChipController implements View.OnClickListener {
public void onClick(View v) {
if (v == mChipView) {
recordChipEvent(ChipEvent.CLICKED);
mChipClickedCallback.run();
mLensAsyncManager.searchWithGoogleLens();
dismissLensChipIfShowing();
}
}
......
......@@ -116,11 +116,7 @@ public class RevampedContextMenuCoordinator implements ContextMenuUi {
if (params.isImage() && lensShoppingFeatureEnabled && !isPopup) {
View chipAnchorView = layout.findViewById(R.id.context_menu_chip_anchor_point);
mChipController = new RevampedContextMenuChipController(
activity, chipAnchorView, lensAsyncManager, () -> {
// A chip selection should trigger the lens shopping action.
clickItem((int) R.id.contextmenu_shop_image_with_google_lens, activity,
onItemClicked);
});
activity, chipAnchorView, lensAsyncManager);
dialogBottomMarginPx = mChipController.getVerticalPxNeededForChip();
// Allow dialog to get close to the top of the screen.
dialogTopMarginPx = dialogBottomMarginPx / 2;
......@@ -286,7 +282,8 @@ public class RevampedContextMenuCoordinator implements ContextMenuUi {
void simulateShoppyImageClassificationForTesting() {
// Don't need to initialize controller because that should be triggered by
// forcing feature flags.
mChipController.handleImageClassification(true);
mChipController.setFakeLensQueryResultForTesting(); // IN-TEST
mChipController.handleImageClassification(null);
}
// Public only to allow references from RevampedContextMenuUtils.java
......
......@@ -13,7 +13,6 @@ import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.ContextUtils;
......@@ -21,6 +20,7 @@ import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.externalauth.ExternalAuthUtils;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.lens.LensController;
import org.chromium.chrome.browser.lens.LensQueryResult;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.IdentityServicesProvider;
import org.chromium.components.signin.base.CoreAccountInfo;
......@@ -61,7 +61,7 @@ public class LensUtils {
"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 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_DEFAULT_SHOPPING_URL_PATTERNS =
"^https://www.google.com/shopping/.*|^https://www.google.com/.*tbm=shop.*";
......@@ -72,15 +72,6 @@ public class LensUtils {
private static boolean sFakePassableLensEnvironmentForTesting;
private static boolean sFakeImageUrlInShoppingAllowlistForTesting;
private static String sFakeVariationsForTesting;
/** Supported Lens intent types. */
@IntDef({
IntentType.DEFAULT,
IntentType.SHOPPING,
})
public @interface IntentType {
int DEFAULT = 0;
int SHOPPING = 1;
}
/*
* If true, short-circuit the version name intent check to always return a high enough version.
......@@ -133,7 +124,7 @@ public class LensUtils {
final Intent lensIntent =
getShareWithGoogleLensIntent(Uri.EMPTY, /* isIncognito= */ false,
/* currentTimeNanos= */ 0L, /* srcUrl */ "",
/* titleOrAltText */ "", /* intentType */ IntentType.DEFAULT,
/* titleOrAltText */ "", /* lensQueryResult */ null,
/* requiresConfirmation */ false);
final ComponentName lensActivity = lensIntent.resolveActivity(pm);
if (lensActivity == null) return "";
......@@ -242,14 +233,14 @@ public class LensUtils {
* @param srcUrl The 'src' attribute of the image.
* @param titleOrAltText The 'title' or, if empty, the 'alt' attribute of the
* image.
* @param intentType The type of the intent.
* @param LensQueryResult The image query result returned from Lens Prime API.
* @param requiresConfirmation Whether the request requires an confirmation dialog.
* @return The intent to Google Lens.
*/
public static Intent getShareWithGoogleLensIntent(final Uri imageUri, final boolean isIncognito,
final long currentTimeNanos, final String srcUrl, final String titleOrAltText,
@IntentType final int intentType, final boolean requiresConfirmation) {
LensQueryResult lensQueryResult, final boolean requiresConfirmation) {
final CoreAccountInfo coreAccountInfo =
IdentityServicesProvider.get()
.getIdentityManager(Profile.getLastUsedRegularProfile())
......@@ -270,9 +261,11 @@ public class LensUtils {
.appendQueryParameter(
LAUNCH_TIMESTAMP_URI_KEY, Long.toString(currentTimeNanos));
if (intentType == IntentType.SHOPPING) {
if (lensQueryResult != null
&& (lensQueryResult.getIsShoppyIntent()
|| isLensShoppingIntentType(lensQueryResult.getLensIntentType()))) {
lensUriBuilder.appendQueryParameter(
LENS_INTENT_TYPE_KEY, LENS_INTENT_TYPE_LENS_CHROME_SHOPPING);
LENS_INTENT_TYPE_KEY, Integer.toString(getLensShoppingIntentType()));
}
if (requiresConfirmation) {
......@@ -498,6 +491,21 @@ public class LensUtils {
return false;
}
/**
* @return the Lens shopping intent type integer.
*/
public static int getLensShoppingIntentType() {
return LENS_INTENT_TYPE_LENS_CHROME_SHOPPING;
}
/**
* Check if the the intent type is Lens shopping intent type.
* @return true if the intent type is shopping.
*/
public static boolean isLensShoppingIntentType(int intentType) {
return intentType == getLensShoppingIntentType();
}
/**
* Check if the uri matches any shopping url patterns.
*/
......
......@@ -27,6 +27,7 @@ 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.LensQueryResult;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
import org.chromium.components.browser_ui.share.ShareParams;
......@@ -124,15 +125,15 @@ public class ShareHelper extends org.chromium.components.browser_ui.share.ShareH
* @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 isShoppingIntent Whether the Lens intent is a shopping intent.
* @param lensQueryResult The wrapper object which contains the classify result of Lens image
* query.
* @param requiresConfirmation Whether the request requires an confirmation dialog.
*/
public static void shareImageWithGoogleLens(final WindowAndroid window, Uri imageUri,
boolean isIncognito, String srcUrl, String titleOrAltText, boolean isShoppingIntent,
boolean requiresConfirmation) {
boolean isIncognito, String srcUrl, String titleOrAltText,
LensQueryResult lensQueryResult, boolean requiresConfirmation) {
Intent shareIntent = LensUtils.getShareWithGoogleLensIntent(imageUri, isIncognito,
SystemClock.elapsedRealtimeNanos(), srcUrl, titleOrAltText,
isShoppingIntent ? LensUtils.IntentType.SHOPPING : LensUtils.IntentType.DEFAULT,
SystemClock.elapsedRealtimeNanos(), srcUrl, titleOrAltText, lensQueryResult,
requiresConfirmation);
try {
// Pass an empty callback to ensure the triggered activity can identify the source
......
......@@ -74,6 +74,11 @@ public class TabContextMenuItemDelegate implements ContextMenuItemDelegate {
mTab.removeObserver(mDataReductionProxyContextMenuTabObserver);
}
@Override
public String getPageTitle() {
return mTab.getTitle();
}
@Override
public WebContents getWebContents() {
return mTab.getWebContents();
......
......@@ -57,4 +57,9 @@ public class TabContextMenuPopulator implements ContextMenuPopulator {
public boolean isIncognito() {
return mPopulator.isIncognito();
}
@Override
public String getPageTitle() {
return mPopulator.getPageTitle();
}
}
......@@ -21,6 +21,8 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.lens.LensQueryResult;
import org.chromium.chrome.browser.share.LensUtils;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.ui.test.util.DummyUiActivity;
......@@ -68,10 +70,11 @@ public class RevampedContextMenuChipControllerTest extends DummyUiActivityTestCa
@SmallTest
public void testChipNotShownWhenCallbackReturnsFalse() {
RevampedContextMenuChipController chipController = new RevampedContextMenuChipController(
getActivity(), mAnchorView, mLensAsyncManager, () -> {});
getActivity(), mAnchorView, mLensAsyncManager);
TestThreadUtils.runOnUiThreadBlocking(
() -> { chipController.handleImageClassification(false); });
TestThreadUtils.runOnUiThreadBlocking(() -> {
chipController.handleImageClassification((new LensQueryResult.Builder()).build());
});
assertNotNull("Anchor view was not initialized.", mAnchorView);
assertNull("Popup window was initialized unexpectedly.",
......@@ -82,9 +85,14 @@ public class RevampedContextMenuChipControllerTest extends DummyUiActivityTestCa
@SmallTest
public void testChipShownWhenCallbackReturnsTrue() {
RevampedContextMenuChipController chipController = new RevampedContextMenuChipController(
getActivity(), mAnchorView, mLensAsyncManager, () -> {});
TestThreadUtils.runOnUiThreadBlocking(
() -> { chipController.handleImageClassification(true); });
getActivity(), mAnchorView, mLensAsyncManager);
TestThreadUtils.runOnUiThreadBlocking(() -> {
chipController.handleImageClassification(
(new LensQueryResult.Builder())
.withIsShoppyIntent(true)
.withLensIntentType(LensUtils.getLensShoppingIntentType())
.build());
});
assertNotNull("Anchor view was not initialized.", mAnchorView);
assertNotNull("Popup window was not initialized.",
......@@ -97,7 +105,7 @@ public class RevampedContextMenuChipControllerTest extends DummyUiActivityTestCa
@SmallTest
public void testDismissChipWhenNotShownBeforeClassificationReturned() {
RevampedContextMenuChipController chipController = new RevampedContextMenuChipController(
getActivity(), mAnchorView, mLensAsyncManager, () -> {});
getActivity(), mAnchorView, mLensAsyncManager);
TestThreadUtils.runOnUiThreadBlocking(() -> { chipController.dismissLensChipIfShowing(); });
assertNotNull("Anchor view was not initialized.", mAnchorView);
......@@ -109,9 +117,13 @@ public class RevampedContextMenuChipControllerTest extends DummyUiActivityTestCa
@SmallTest
public void testDismissChipWhenShown() {
RevampedContextMenuChipController chipController = new RevampedContextMenuChipController(
getActivity(), mAnchorView, mLensAsyncManager, () -> {});
getActivity(), mAnchorView, mLensAsyncManager);
TestThreadUtils.runOnUiThreadBlocking(() -> {
chipController.handleImageClassification(true);
chipController.handleImageClassification(
(new LensQueryResult.Builder())
.withIsShoppyIntent(true)
.withLensIntentType(LensUtils.getLensShoppingIntentType())
.build());
chipController.dismissLensChipIfShowing();
});
......@@ -126,7 +138,7 @@ public class RevampedContextMenuChipControllerTest extends DummyUiActivityTestCa
@SmallTest
public void testExpectedVerticalPxNeededForChip() {
RevampedContextMenuChipController chipController = new RevampedContextMenuChipController(
getActivity(), mAnchorView, mLensAsyncManager, () -> {});
getActivity(), mAnchorView, mLensAsyncManager);
assertEquals("Vertical px is not matching the expectation",
(int) (EXPECTED_VERTICAL_DP * mMeasuredDeviceDensity),
chipController.getVerticalPxNeededForChip());
......@@ -136,7 +148,7 @@ public class RevampedContextMenuChipControllerTest extends DummyUiActivityTestCa
@SmallTest
public void testExpectedChipTextMaxWidthPx() {
RevampedContextMenuChipController chipController = new RevampedContextMenuChipController(
getActivity(), mAnchorView, mLensAsyncManager, () -> {});
getActivity(), mAnchorView, mLensAsyncManager);
assertEquals("Vertical px is not matching the expectation",
(int) (EXPECTEED_CHIP_WIDTH_DP * mMeasuredDeviceDensity),
chipController.getChipTextMaxWidthPx());
......
......@@ -422,19 +422,16 @@ public class RevampedContextMenuTest implements DownloadTestRule.CustomMainActiv
RevampedContextMenuCoordinator menuCoordinator =
RevampedContextMenuUtils.openContextMenu(tab, "testImage");
// Needs to run on UI thread so creation happens on same thread as dismissal.
TestThreadUtils.runOnUiThreadBlocking(
() -> menuCoordinator.simulateShoppyImageClassificationForTesting());
Assert.assertTrue("Chip popoup not showing.",
menuCoordinator.getCurrentPopupWindowForTesting().isShowing());
TestThreadUtils.runOnUiThreadBlocking(() -> {
menuCoordinator.simulateShoppyImageClassificationForTesting();
Assert.assertTrue("Chip popoup not showing.",
menuCoordinator.getCurrentPopupWindowForTesting().isShowing());
menuCoordinator.clickChipForTesting();
});
RevampedContextMenuUtils.selectAlreadyOpenedContextMenuChipWithExpectedIntent(
InstrumentationRegistry.getInstrumentation(), mDownloadTestRule.getActivity(),
menuCoordinator, "testImage", R.id.contextmenu_shop_image_with_google_lens,
"com.google.android.googlequicksearchbox");
Assert.assertEquals("Selection histogram pings not equal to one", 1,
RecordHistogram.getHistogramTotalCountForTesting(
"ContextMenu.SelectedOptionAndroid.Image"));
RecordHistogram.getHistogramValueCountForTesting("ContextMenu.LensChip.Event",
RevampedContextMenuChipController.ChipEvent.CLICKED));
Assert.assertFalse("Chip popoup still showing.",
menuCoordinator.getCurrentPopupWindowForTesting().isShowing());
}
......
......@@ -32,6 +32,11 @@ public interface ContextMenuItemDelegate {
*/
void onDestroy();
/**
* @return The title of the current tab associated with this delegate..
*/
String getPageTitle();
/**
* @return The web contents of the current tab owned by this delegate.
*/
......
......@@ -42,4 +42,9 @@ public interface ContextMenuPopulator {
* Determines whether the the containing browser is switched to incognito mode.
*/
boolean isIncognito();
/**
* @return The title of current web page.
*/
String getPageTitle();
}
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