Initial support for camera assisted search with Google Lens in Omnibox and NTP

Design doc:go/lens-chrome-omnibox-ntp
Screenshot: https://screenshot.googleplex.com/5idKZZSfmGxs6ca

Change-Id: Ie799e6186e806d0a21d6f0f17bf83c66bfb242ea
Bug: 1175230,b/180518516
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2693505
Reviewed-by: Filip Gorski <[email protected]>
Reviewed-by: Yusuf Ozuysal <[email protected]>
Reviewed-by: Brandon Wylie <[email protected]>
Reviewed-by: Ben Goldberger <[email protected]>
Commit-Queue: Yu Su <[email protected]>
Cr-Commit-Position: refs/heads/master@{#857561}
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
index 07466c1..51f3783b 100644
--- a/chrome/android/chrome_java_resources.gni
+++ b/chrome/android/chrome_java_resources.gni
@@ -646,6 +646,7 @@
   "java/res/drawable/infobar_downloading_fill_animation.xml",
   "java/res/drawable/infobar_downloading_sweep_animation.xml",
   "java/res/drawable/item_chooser_row_background.xml",
+  "java/res/drawable/lens_camera_icon.xml",
   "java/res/drawable/lens_icon.xml",
   "java/res/drawable/logo_partly_cloudy.xml",
   "java/res/drawable/logo_translate_round.xml",
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java
index 7a19237..db9fe5aa 100644
--- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java
+++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java
@@ -32,6 +32,7 @@
 import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
 import org.chromium.components.user_prefs.UserPrefs;
 import org.chromium.ui.base.ViewUtils;
+import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
@@ -50,6 +51,7 @@
     private final @SurfaceMode int mSurfaceMode;
     private final BottomSheetController mBottomSheetController;
     private final Supplier<Tab> mParentTabSupplier;
+    private final WindowAndroid mWindowAndroid;
 
     // Non-null in SurfaceMode.TASKS_ONLY, SurfaceMode.TWO_PANES and SurfaceMode.SINGLE_PANE modes.
     @Nullable
@@ -142,12 +144,13 @@
     public StartSurfaceCoordinator(ChromeActivity activity, ScrimCoordinator scrimCoordinator,
             BottomSheetController sheetController,
             OneshotSupplierImpl<StartSurface> startSurfaceOneshotSupplier,
-            Supplier<Tab> parentTabSupplier, boolean hadWarmStart) {
+            Supplier<Tab> parentTabSupplier, boolean hadWarmStart, WindowAndroid windowAndroid) {
         mActivity = activity;
         mScrimCoordinator = scrimCoordinator;
         mSurfaceMode = computeSurfaceMode();
         mBottomSheetController = sheetController;
         mParentTabSupplier = parentTabSupplier;
+        mWindowAndroid = windowAndroid;
 
         boolean excludeMVTiles = StartSurfaceConfiguration.START_SURFACE_EXCLUDE_MV_TILES.getValue()
                 || mSurfaceMode == SurfaceMode.OMNIBOX_ONLY
@@ -398,7 +401,7 @@
         }
         mTasksSurface = TabManagementModuleProvider.getDelegate().createTasksSurface(mActivity,
                 mScrimCoordinator, mPropertyModel, tabSwitcherType, mParentTabSupplier,
-                !excludeMVTiles, hasTrendyTerms);
+                !excludeMVTiles, hasTrendyTerms, mWindowAndroid);
         mTasksSurface.getView().setId(R.id.primary_tasks_surface_view);
         mTasksSurface.addFakeSearchBoxShrinkAnimation();
         mOffsetChangedListenerToGenerateScrollEvents = new AppBarLayout.OnOffsetChangedListener() {
@@ -438,7 +441,7 @@
         mStartSurfaceMediator.setSecondaryTasksSurfacePropertyModel(propertyModel);
         mSecondaryTasksSurface = TabManagementModuleProvider.getDelegate().createTasksSurface(
                 mActivity, mScrimCoordinator, propertyModel, TabSwitcherType.GRID,
-                mParentTabSupplier, false, false);
+                mParentTabSupplier, false, false, mWindowAndroid);
         if (mIsInitializedWithNative) {
             mSecondaryTasksSurface.onFinishNativeInitialization(
                     mActivity, mActivity.getToolbarManager().getFakeboxDelegate());
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceDelegate.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceDelegate.java
index 3e15775..516863c 100644
--- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceDelegate.java
+++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceDelegate.java
@@ -15,6 +15,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
+import org.chromium.ui.base.WindowAndroid;
 
 /** StartSurfaceDelegate. */
 public class StartSurfaceDelegate {
@@ -26,8 +27,8 @@
     public static StartSurface createStartSurface(ChromeActivity activity,
             ScrimCoordinator scrimCoordinator, BottomSheetController sheetController,
             OneshotSupplierImpl<StartSurface> startSurfaceOneshotSupplier,
-            Supplier<Tab> parentTabSupplier, boolean hadWarmStart) {
+            Supplier<Tab> parentTabSupplier, boolean hadWarmStart, WindowAndroid windowAndroid) {
         return new StartSurfaceCoordinator(activity, scrimCoordinator, sheetController,
-                startSurfaceOneshotSupplier, parentTabSupplier, hadWarmStart);
+                startSurfaceOneshotSupplier, parentTabSupplier, hadWarmStart, windowAndroid);
     }
 }
\ No newline at end of file
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
index 9817518..a4d1caa 100644
--- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
+++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
@@ -8,6 +8,7 @@
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_INCOGNITO;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_INCOGNITO_DESCRIPTION_INITIALIZED;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_INCOGNITO_DESCRIPTION_VISIBLE;
+import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_LENS_BUTTON_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_SURFACE_BODY_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_TAB_CAROUSEL_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_VOICE_RECOGNITION_BUTTON_VISIBLE;
@@ -48,6 +49,7 @@
 import org.chromium.chrome.browser.feed.shared.stream.Stream;
 import org.chromium.chrome.browser.flags.CachedFeatureFlags;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.lens.LensEntryPoint;
 import org.chromium.chrome.browser.ntp.FakeboxDelegate;
 import org.chromium.chrome.browser.omnibox.UrlFocusChangeListener;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
@@ -920,6 +922,8 @@
             if (mFakeboxDelegate != null && mFakeboxDelegate.getVoiceRecognitionHandler() != null) {
                 mPropertyModel.set(IS_VOICE_RECOGNITION_BUTTON_VISIBLE,
                         mFakeboxDelegate.getVoiceRecognitionHandler().isVoiceSearchEnabled());
+                mPropertyModel.set(IS_LENS_BUTTON_VISIBLE,
+                        mFakeboxDelegate.isLensEnabled(LensEntryPoint.TASKS_SURFACE));
             }
         });
     }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceCoordinator.java
index e9fd02c..e8140aa 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceCoordinator.java
@@ -27,6 +27,7 @@
 import org.chromium.chrome.browser.tasks.tab_management.TabSwitcher;
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
+import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
@@ -47,9 +48,12 @@
 
     public TasksSurfaceCoordinator(ChromeActivity activity, ScrimCoordinator scrimCoordinator,
             PropertyModel propertyModel, @TabSwitcherType int tabSwitcherType,
-            Supplier<Tab> parentTabSupplier, boolean hasMVTiles, boolean hasTrendyTerms) {
+            Supplier<Tab> parentTabSupplier, boolean hasMVTiles, boolean hasTrendyTerms,
+            WindowAndroid windowAndroid) {
         mView = (TasksView) LayoutInflater.from(activity).inflate(R.layout.tasks_view_layout, null);
-        mView.initialize(activity.getLifecycleDispatcher());
+        mView.initialize(activity.getLifecycleDispatcher(),
+                parentTabSupplier.hasValue() ? parentTabSupplier.get().isIncognito() : false,
+                windowAndroid);
         mPropertyModelChangeProcessor =
                 PropertyModelChangeProcessor.create(propertyModel, mView, TasksViewBinder::bind);
         mPropertyModel = propertyModel;
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceMediator.java
index 8708289..a76b20e 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceMediator.java
@@ -14,9 +14,11 @@
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.INCOGNITO_COOKIE_CONTROLS_TOGGLE_ENFORCEMENT;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.INCOGNITO_LEARN_MORE_CLICK_LISTENER;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_FAKE_SEARCH_BOX_VISIBLE;
+import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_LENS_BUTTON_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_SURFACE_BODY_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_TAB_CAROUSEL_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_VOICE_RECOGNITION_BUTTON_VISIBLE;
+import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.LENS_BUTTON_CLICK_LISTENER;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.VOICE_SEARCH_BUTTON_CLICK_LISTENER;
 
 import android.text.Editable;
@@ -26,6 +28,7 @@
 import androidx.annotation.Nullable;
 
 import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.chrome.browser.lens.LensEntryPoint;
 import org.chromium.chrome.browser.ntp.FakeboxDelegate;
 import org.chromium.chrome.browser.ntp.IncognitoCookieControlsManager;
 import org.chromium.chrome.browser.omnibox.OmniboxFocusReason;
@@ -62,6 +65,7 @@
         mModel.set(IS_SURFACE_BODY_VISIBLE, true);
         mModel.set(IS_FAKE_SEARCH_BOX_VISIBLE, true);
         mModel.set(IS_VOICE_RECOGNITION_BUTTON_VISIBLE, false);
+        mModel.set(IS_LENS_BUTTON_VISIBLE, false);
     }
 
     public void initWithNative(FakeboxDelegate fakeboxDelegate) {
@@ -103,6 +107,14 @@
             }
         });
 
+        mModel.set(LENS_BUTTON_CLICK_LISTENER, new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                // TODO(b/181067692): Report user action for this click.
+                mFakeboxDelegate.startLens(LensEntryPoint.TASKS_SURFACE);
+            }
+        });
+
         mIncognitoCookieControlsObserver = new IncognitoCookieControlsManager.Observer() {
             @Override
             public void onUpdate(boolean checked, @CookieControlsEnforcement int enforcement) {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceProperties.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceProperties.java
index 5a034a9..b1ca0a9 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceProperties.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceProperties.java
@@ -27,6 +27,8 @@
             new PropertyModel.WritableBooleanPropertyKey();
     public static final PropertyModel.WritableBooleanPropertyKey IS_INCOGNITO_DESCRIPTION_VISIBLE =
             new PropertyModel.WritableBooleanPropertyKey();
+    public static final PropertyModel.WritableBooleanPropertyKey IS_LENS_BUTTON_VISIBLE =
+            new PropertyModel.WritableBooleanPropertyKey();
     public static final PropertyModel.WritableBooleanPropertyKey IS_SURFACE_BODY_VISIBLE =
             new PropertyModel.WritableBooleanPropertyKey();
     public static final PropertyModel.WritableBooleanPropertyKey IS_TAB_CAROUSEL_VISIBLE =
@@ -61,6 +63,9 @@
             .WritableObjectPropertyKey<TextWatcher> FAKE_SEARCH_BOX_TEXT_WATCHER =
             new PropertyModel.WritableObjectPropertyKey<>();
     public static final PropertyModel
+            .WritableObjectPropertyKey<View.OnClickListener> LENS_BUTTON_CLICK_LISTENER =
+            new PropertyModel.WritableObjectPropertyKey<>();
+    public static final PropertyModel
             .WritableObjectPropertyKey<View.OnClickListener> MORE_TABS_CLICK_LISTENER =
             new PropertyModel.WritableObjectPropertyKey<>();
     public static final PropertyModel.WritableBooleanPropertyKey MV_TILES_VISIBLE = IS_VISIBLE;
@@ -80,14 +85,14 @@
             new PropertyModel.WritableObjectPropertyKey<>(true /* skipEquality */);
     public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {IS_FAKE_SEARCH_BOX_VISIBLE,
             IS_INCOGNITO, IS_INCOGNITO_DESCRIPTION_INITIALIZED, IS_INCOGNITO_DESCRIPTION_VISIBLE,
-            IS_SURFACE_BODY_VISIBLE, IS_TAB_CAROUSEL_VISIBLE, IS_VOICE_RECOGNITION_BUTTON_VISIBLE,
-            INCOGNITO_COOKIE_CONTROLS_CARD_VISIBILITY,
+            IS_LENS_BUTTON_VISIBLE, IS_SURFACE_BODY_VISIBLE, IS_TAB_CAROUSEL_VISIBLE,
+            IS_VOICE_RECOGNITION_BUTTON_VISIBLE, INCOGNITO_COOKIE_CONTROLS_CARD_VISIBILITY,
             INCOGNITO_COOKIE_CONTROLS_ICON_CLICK_LISTENER, INCOGNITO_COOKIE_CONTROLS_TOGGLE_CHECKED,
             INCOGNITO_COOKIE_CONTROLS_TOGGLE_CHECKED_LISTENER,
             INCOGNITO_COOKIE_CONTROLS_TOGGLE_ENFORCEMENT, INCOGNITO_COOKIE_CONTROLS_MANAGER,
             INCOGNITO_LEARN_MORE_CLICK_LISTENER, FAKE_SEARCH_BOX_CLICK_LISTENER,
-            FAKE_SEARCH_BOX_TEXT_WATCHER, MORE_TABS_CLICK_LISTENER, MV_TILES_VISIBLE,
-            VOICE_SEARCH_BUTTON_CLICK_LISTENER, TASKS_SURFACE_BODY_TOP_MARGIN,
+            FAKE_SEARCH_BOX_TEXT_WATCHER, LENS_BUTTON_CLICK_LISTENER, MORE_TABS_CLICK_LISTENER,
+            MV_TILES_VISIBLE, VOICE_SEARCH_BUTTON_CLICK_LISTENER, TASKS_SURFACE_BODY_TOP_MARGIN,
             MV_TILES_CONTAINER_TOP_MARGIN, TAB_SWITCHER_TITLE_TOP_MARGIN, TRENDY_TERMS_VISIBLE,
             RESET_TASK_SURFACE_HEADER_SCROLL_POSITION};
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksView.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksView.java
index 5f4a5ce..00677dd4 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksView.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksView.java
@@ -42,6 +42,7 @@
 import org.chromium.components.browser_ui.widget.displaystyle.ViewResizer;
 import org.chromium.components.content_settings.CookieControlsEnforcement;
 import org.chromium.ui.base.ViewUtils;
+import org.chromium.ui.base.WindowAndroid;
 
 // The view of the tasks surface.
 class TasksView extends CoordinatorLayoutForPointer {
@@ -69,11 +70,12 @@
         mContext = context;
     }
 
-    public void initialize(ActivityLifecycleDispatcher activityLifecycleDispatcher) {
+    public void initialize(ActivityLifecycleDispatcher activityLifecycleDispatcher,
+            boolean isIncognito, WindowAndroid windowAndroid) {
         assert mSearchBoxCoordinator
                 != null : "#onFinishInflate should be completed before the call to initialize.";
 
-        mSearchBoxCoordinator.initialize(activityLifecycleDispatcher);
+        mSearchBoxCoordinator.initialize(activityLifecycleDispatcher, isIncognito, windowAndroid);
     }
 
     @Override
@@ -203,6 +205,7 @@
         setBackgroundColor(backgroundColor);
         mHeaderView.setBackgroundColor(backgroundColor);
 
+        mSearchBoxCoordinator.setIncognitoMode(isIncognito);
         mSearchBoxCoordinator.setBackground(AppCompatResources.getDrawable(mContext,
                 isIncognito ? R.drawable.fake_search_box_bg_incognito : R.drawable.ntp_search_box));
         int hintTextColor = isIncognito
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksViewBinder.java
index 971be0d..e526b59d 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksViewBinder.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksViewBinder.java
@@ -17,9 +17,11 @@
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_INCOGNITO;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_INCOGNITO_DESCRIPTION_INITIALIZED;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_INCOGNITO_DESCRIPTION_VISIBLE;
+import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_LENS_BUTTON_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_SURFACE_BODY_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_TAB_CAROUSEL_VISIBLE;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_VOICE_RECOGNITION_BUTTON_VISIBLE;
+import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.LENS_BUTTON_CLICK_LISTENER;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.MORE_TABS_CLICK_LISTENER;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.MV_TILES_CONTAINER_TOP_MARGIN;
 import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.MV_TILES_VISIBLE;
@@ -77,6 +79,9 @@
                 model.get(INCOGNITO_COOKIE_CONTROLS_MANAGER).updateIfNecessary();
             }
             view.setIncognitoDescriptionVisibility(isVisible);
+        } else if (propertyKey == IS_LENS_BUTTON_VISIBLE) {
+            view.getSearchBoxCoordinator().setLensButtonVisibility(
+                    model.get(IS_LENS_BUTTON_VISIBLE));
         } else if (propertyKey == IS_SURFACE_BODY_VISIBLE) {
             view.setSurfaceBodyVisibility(model.get(IS_SURFACE_BODY_VISIBLE));
         } else if (propertyKey == IS_TAB_CAROUSEL_VISIBLE) {
@@ -84,6 +89,9 @@
         } else if (propertyKey == IS_VOICE_RECOGNITION_BUTTON_VISIBLE) {
             view.getSearchBoxCoordinator().setVoiceSearchButtonVisibility(
                     model.get(IS_VOICE_RECOGNITION_BUTTON_VISIBLE));
+        } else if (propertyKey == LENS_BUTTON_CLICK_LISTENER) {
+            view.getSearchBoxCoordinator().addLensButtonClickListener(
+                    model.get(LENS_BUTTON_CLICK_LISTENER));
         } else if (propertyKey == MORE_TABS_CLICK_LISTENER) {
             view.setMoreTabsOnClickListener(model.get(MORE_TABS_CLICK_LISTENER));
         } else if (propertyKey == MV_TILES_VISIBLE) {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegate.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegate.java
index 6a04f9f..05bab111 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegate.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegate.java
@@ -27,6 +27,7 @@
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
 import org.chromium.components.module_installer.builder.ModuleInterface;
+import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modelutil.PropertyModel;
 
 import java.lang.annotation.Retention;
@@ -60,11 +61,13 @@
      *         TasksSurface.
      * @param hasMVTiles whether has MV tiles on the surface.
      * @param hasTrendyTerms whether has trendy terms on the surface.
+     * @param windowAndroid An instance of a {@link WindowAndroid}
      * @return The {@link TasksSurface}.
      */
     TasksSurface createTasksSurface(ChromeActivity activity, ScrimCoordinator scrimCoordinator,
             PropertyModel propertyModel, @TabSwitcherType int tabSwitcherType,
-            Supplier<Tab> parentTabSupplier, boolean hasMVTiles, boolean hasTrendyTerms);
+            Supplier<Tab> parentTabSupplier, boolean hasMVTiles, boolean hasTrendyTerms,
+            WindowAndroid windowAndroid);
 
     /**
      * Create the {@link TabSwitcher} to display Tabs in grid.
@@ -118,12 +121,13 @@
      *         StartSurface.
      * @param hadWarmStart Whether the activity had a warm start because the native library was
      *         already fully loaded and initialized
+     * @param windowAndroid An instance of a {@link WindowAndroid}
      * @return the {@link StartSurface}
      */
     StartSurface createStartSurface(ChromeActivity activity, ScrimCoordinator scrimCoordinator,
             BottomSheetController sheetController,
             OneshotSupplierImpl<StartSurface> startSurfaceOneshotSupplier,
-            Supplier<Tab> parentTabSupplier, boolean hadWarmStart);
+            Supplier<Tab> parentTabSupplier, boolean hadWarmStart, WindowAndroid windowAndroid);
 
     /**
      * Create a {@link TabGroupModelFilter} for the given {@link TabModel}.
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegateImpl.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegateImpl.java
index fb82f28..7995601 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegateImpl.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegateImpl.java
@@ -32,6 +32,7 @@
 import org.chromium.chrome.features.start_surface.StartSurfaceDelegate;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
+import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modelutil.PropertyModel;
 
 /**
@@ -43,9 +44,9 @@
     public TasksSurface createTasksSurface(ChromeActivity activity,
             ScrimCoordinator scrimCoordinator, PropertyModel propertyModel,
             @TabSwitcherType int tabSwitcherType, Supplier<Tab> parentTabSupplier,
-            boolean hasMVTiles, boolean hasTrendyTerms) {
+            boolean hasMVTiles, boolean hasTrendyTerms, WindowAndroid windowAndroid) {
         return new TasksSurfaceCoordinator(activity, scrimCoordinator, propertyModel,
-                tabSwitcherType, parentTabSupplier, hasMVTiles, hasTrendyTerms);
+                tabSwitcherType, parentTabSupplier, hasMVTiles, hasTrendyTerms, windowAndroid);
     }
 
     @Override
@@ -99,9 +100,9 @@
     public StartSurface createStartSurface(ChromeActivity activity,
             ScrimCoordinator scrimCoordinator, BottomSheetController sheetController,
             OneshotSupplierImpl<StartSurface> startSurfaceOneshotSupplier,
-            Supplier<Tab> parentTabSupplier, boolean hadWarmStart) {
+            Supplier<Tab> parentTabSupplier, boolean hadWarmStart, WindowAndroid windowAndroid) {
         return StartSurfaceDelegate.createStartSurface(activity, scrimCoordinator, sheetController,
-                startSurfaceOneshotSupplier, parentTabSupplier, hadWarmStart);
+                startSurfaceOneshotSupplier, parentTabSupplier, hadWarmStart, windowAndroid);
     }
 
     @Override
diff --git a/chrome/android/java/res/drawable/lens_camera_icon.xml b/chrome/android/java/res/drawable/lens_camera_icon.xml
new file mode 100644
index 0000000..183b82d
--- /dev/null
+++ b/chrome/android/java/res/drawable/lens_camera_icon.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" />
+    <path
+        android:fillColor="#4285F4"
+        android:pathData="M 12 9.52 C 13.9329966244 9.52 15.5 11.0870033756 15.5 13.02 C 15.5 14.9529966244 13.9329966244 16.52 12 16.52 C 10.0670033756 16.52 8.5 14.9529966244 8.5 13.02 C 8.5 11.0870033756 10.0670033756 9.52 12 9.52 Z" />
+    <path
+        android:fillColor="#EA4335"
+        android:pathData="M20,9v5.02V17c0,0.21-0.04,0.41-0.1,0.6v0v0c-0.2,0.62-0.68,1.1-1.29,1.29h0v0 C18.41,18.96,18.21,19,18,19H8.99l2,2h5.51H18c0.55,0,1.08-0.11,1.56-0.31c0.48-0.2,0.91-0.5,1.27-0.86 c0.18-0.18,0.34-0.38,0.49-0.59c0.29-0.43,0.5-0.91,0.6-1.43C21.97,17.55,22,17.28,22,17v-1.5v-3.48V10.5l-2.1-2.1 C19.96,8.59,20,8.79,20,9z" />
+    <path
+        android:fillColor="#4285F4"
+        android:pathData="M4,9c0-0.21,0.04-0.41,0.1-0.6C4.3,7.78,4.78,7.3,5.4,7.1C5.59,7.04,5.79,7,6,7h12 c0.21,0,0.41,0.04,0.6,0.1c0,0,0,0,0,0L16.5,5l-2-2h-1.68H12h-0.82H9.5l-2,2H6C3.79,5,2,6.79,2,9v1.5v1.51l2,2V9z" />
+    <path
+        android:fillColor="#34A853"
+        android:pathData="M18,5h-1.5l2.1,2.1c0.15,0.05,0.3,0.12,0.43,0.2c0.27,0.16,0.5,0.39,0.66,0.66 c0.08,0.13,0.15,0.28,0.2,0.43v0l2.1,2.1V9C22,6.79,20.21,5,18,5z" />
+    <path
+        android:fillColor="#FBBC04"
+        android:pathData="M6,21h4.99l-2-2H6c-1.1,0-2-0.9-2-2v-2.99l-2-2V17C2,19.21,3.79,21,6,21z" />
+</vector>
\ No newline at end of file
diff --git a/chrome/android/java/res/layout/fake_search_box_layout.xml b/chrome/android/java/res/layout/fake_search_box_layout.xml
index 6f113842..9be6648 100644
--- a/chrome/android/java/res/layout/fake_search_box_layout.xml
+++ b/chrome/android/java/res/layout/fake_search_box_layout.xml
@@ -63,4 +63,13 @@
         android:src="@drawable/btn_mic"
         android:scaleType="centerInside"
         app:tint="@color/default_icon_color_tint_list" />
+    <org.chromium.ui.widget.ChromeImageView
+        android:id="@+id/lens_camera_button"
+        android:layout_width="48dp"
+        android:layout_height="match_parent"
+        android:contentDescription="@string/accessibility_btn_lens_camera"
+        android:src="@drawable/lens_camera_icon"
+        android:scaleType="centerInside"
+        android:visibility="gone"
+        app:tint="@color/default_icon_color_tint_list" />
 </view>
diff --git a/chrome/android/java/res/layout/url_action_container.xml b/chrome/android/java/res/layout/url_action_container.xml
index 0c84cbf..dabc56b 100644
--- a/chrome/android/java/res/layout/url_action_container.xml
+++ b/chrome/android/java/res/layout/url_action_container.xml
@@ -28,6 +28,13 @@
             android:contentDescription="@string/accessibility_toolbar_btn_mic" />
 
         <org.chromium.ui.widget.ChromeImageButton
+            android:id="@+id/lens_camera_button"
+            style="@style/LocationBarActionButton"
+            android:src="@drawable/lens_camera_icon"
+            android:visibility="gone"
+            android:contentDescription="@string/accessibility_btn_lens_camera" />
+
+        <org.chromium.ui.widget.ChromeImageButton
             android:id="@+id/bookmark_button"
             style="@style/LocationBarActionButton"
             android:visibility="gone"
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index 20ba0d5..8c479b5 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -286,7 +286,7 @@
     <!-- The ntp_voice_search_button_width is 48dp and its icon is 24dp. Setting
          8dp start padding will result in 8dp padding between the icon and the
          end of the fakebox ((48 - 24 - 8) / 2). -->
-    <dimen name="sei_ntp_voice_button_start_padding">8dp</dimen>
+    <dimen name="sei_ntp_fakebox_button_start_padding">8dp</dimen>
     <dimen name="experimental_explore_sites_radius">8dp</dimen>
     <dimen name="experimental_explore_sites_padding">8dp</dimen>
     <dimen name="experimental_explore_sites_margin">16dp</dimen>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index fca81d2..1e47045e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -611,7 +611,7 @@
                     StartSurface startSurface = tabManagementDelegate.createStartSurface(this,
                             mRootUiCoordinator.getScrimCoordinator(),
                             mRootUiCoordinator.getBottomSheetController(), mStartSurfaceSupplier,
-                            mStartSurfaceParentTabSupplier, hadWarmStart());
+                            mStartSurfaceParentTabSupplier, hadWarmStart(), getWindowAndroid());
                 }
             }
             // clang-format off
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/LensChipDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/LensChipDelegate.java
index 3d1ff0e0..0f00320 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/LensChipDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/LensChipDelegate.java
@@ -30,7 +30,7 @@
             return;
         }
         mLensQueryParams =
-                (new LensQueryParams.Builder(LensEntryPoint.CONTEXT_MENU_CHIP, isIncognito))
+                new LensQueryParams.Builder(LensEntryPoint.CONTEXT_MENU_CHIP, isIncognito)
                         .withPageUrl(pageUrl)
                         .withImageTitleOrAltText(titleOrAltText)
                         .withSrcUrl(srcUrl)
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/lens/LensController.java b/chrome/android/java/src/org/chromium/chrome/browser/lens/LensController.java
index 42f33e97..12a4b526 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/lens/LensController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/lens/LensController.java
@@ -142,7 +142,7 @@
     /** Terminate any active Lens connections. */
     public void terminateLensConnections() {}
 
-    // TODO(yusuyoutube): revisit the wrapper object for this enablement check. LensQueryParams
+    // TODO(b/180960783): Revisit the wrapper object for this enablement check. LensQueryParams
     // was designed to be only used in the Prime classification query.
     /**
      * Whether the Lens is enabled based on user signals.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/lens/LensIntentParams.java b/chrome/android/java/src/org/chromium/chrome/browser/lens/LensIntentParams.java
index 0700ffb..92c6dd6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/lens/LensIntentParams.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/lens/LensIntentParams.java
@@ -35,8 +35,10 @@
 
         public Builder() {}
 
-        // TODO(yusuyoutube): remove the with* methods for the required params once
+        // TODO(b/180967190): remove the with* methods for the required params once
         // downstream references are updated.
+        // lensEntryPoint and isIncognito are required params when creating the
+        // LensIntentParams.
         public Builder(@LensEntryPoint int lensEntryPoint, boolean isIncognito) {
             this();
             this.mLensEntryPoint = lensEntryPoint;
@@ -149,37 +151,37 @@
         }
     }
 
-    /** Retrieve the image URI for the intent. */
+    /** Returns the imageUri for this set of params. */
     public Uri getImageUri() {
         return mImageUri;
     }
 
-    /** Retrieve the page URL for the intent. */
+    /** Returns the pageUrl for this set of params. */
     public String getPageUrl() {
         return mPageUrl;
     }
 
-    /** Retrieve the image source URL for the intent. */
+    /** Returns the srcUrl for this set of params. */
     public String getSrcUrl() {
         return mSrcUrl;
     }
 
-    /** Retrieve the image title or alt text for the intent. */
+    /** Returns the imageTitleOrAltText for this set of params. */
     public String getImageTitleOrAltText() {
         return mImageTitleOrAltText;
     }
 
-    /** Retrieve whether the client is incognito for the intent. */
+    /** Returns the isIncognito for this set of params. */
     public boolean getIsIncognito() {
         return mIsIncognito;
     }
 
-    /** Retrieve whether the client requires account for the intent. */
+    /** Returns the requiresConfirmation for this set of params. */
     public boolean getRequiresConfirmation() {
         return mRequiresConfirmation;
     }
 
-    /** Retrieve the intent type. */
+    /** Returns the intentType for this set of params. */
     public int getIntentType() {
         return mIntentType;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/lens/LensQueryParams.java b/chrome/android/java/src/org/chromium/chrome/browser/lens/LensQueryParams.java
index ed84102..b313fb54 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/lens/LensQueryParams.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/lens/LensQueryParams.java
@@ -6,6 +6,8 @@
 
 import android.net.Uri;
 
+import androidx.annotation.NonNull;
+
 import org.chromium.content_public.browser.WebContents;
 
 /**
@@ -55,31 +57,61 @@
             return this;
         }
 
-        public Builder withImageUri(Uri imageUri) {
+        /**
+         * Sets the image URI.
+         *
+         * @param imageUri The image URI to set as a parameter
+         */
+        public Builder withImageUri(@NonNull Uri imageUri) {
             this.mImageUri = imageUri;
             return this;
         }
 
+        /**
+         * Sets the URL of the top level frame of the page.
+         *
+         * @param pageUrl The page URL string to set as a parameter
+         */
         public Builder withPageUrl(String pageUrl) {
             this.mPageUrl = pageUrl;
             return this;
         }
 
+        /**
+         * Sets the image title or alt text.
+         *
+         * @param imageTitleOrAltText The image title or alt text to set as a parameter
+         */
         public Builder withImageTitleOrAltText(String imageTitleOrAltText) {
             this.mImageTitleOrAltText = imageTitleOrAltText;
             return this;
         }
 
+        /**
+         * Sets the title of the top level frame of the page.
+         *
+         * @param pageTitle The page title string to set as a parameter
+         */
         public Builder withPageTitle(String pageTitle) {
             this.mPageTitle = pageTitle;
             return this;
         }
 
+        /**
+         * Sets the web contents.
+         *
+         * @param webContents The web contents to set as a parameter
+         */
         public Builder withWebContents(WebContents webContents) {
             this.mWebContents = webContents;
             return this;
         }
 
+        /**
+         * Sets the image source URL.
+         *
+         * @param srcUrl The image source URL string to set as a parameter
+         */
         public Builder withSrcUrl(String srcUrl) {
             this.mSrcUrl = srcUrl;
             return this;
@@ -90,6 +122,9 @@
             return this;
         }
 
+        /**
+         * Build LensQueryParams object from parameters set.
+         */
         public LensQueryParams build() {
             LensQueryParams lensQueryParams = new LensQueryParams();
             lensQueryParams.mLensEntryPoint = this.mLensEntryPoint;
@@ -104,34 +139,47 @@
         }
     }
 
-    public void setImageUri(Uri imageUri) {
+    /**
+     * Sets the imageUri.
+     * With this setter method we can set the imageUri in a retrieve image callback.
+     * e.g., LensChipDelegate#getChipRenderParams.
+     * @param imageUri The image URI to set as a parameter
+     */
+    public void setImageUri(@NonNull Uri imageUri) {
         mImageUri = imageUri;
     }
 
+    /** Returns the image URI for this set of params. */
     public Uri getImageUri() {
         return mImageUri;
     }
 
+    /** Returns the page url for this set of params. */
     public String getPageUrl() {
         return mPageUrl;
     }
 
+    /** Returns the image title or alt text for this set of params. */
     public String getImageTitleOrAltText() {
         return mImageTitleOrAltText;
     }
 
+    /** Returns the page title for this set of params. */
     public String getPageTitle() {
         return mPageTitle;
     }
 
+    /** Returns the web contents for this set of params. */
     public WebContents getWebContents() {
         return mWebContents;
     }
 
+    /** Returns the src url for this set of params. */
     public String getSrcUrl() {
         return mSrcUrl;
     }
 
+    /** Returns the isIncognito for this set of params. */
     public boolean getIsIncognito() {
         return mIsIncognito;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/FakeboxDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/FakeboxDelegate.java
index b11e4b6a..b13acb9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/FakeboxDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/FakeboxDelegate.java
@@ -6,6 +6,7 @@
 
 import androidx.annotation.Nullable;
 
+import org.chromium.chrome.browser.lens.LensEntryPoint;
 import org.chromium.chrome.browser.omnibox.OmniboxFocusReason;
 import org.chromium.chrome.browser.omnibox.UrlFocusChangeListener;
 import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler;
@@ -59,4 +60,15 @@
      * @param listener The listener to be removed.
      */
     default void removeUrlFocusChangeListener(UrlFocusChangeListener listener) {}
+
+    /**
+     * Returns whether the Lens is currently enabled.
+     */
+    boolean isLensEnabled(@LensEntryPoint int lensEntryPoint);
+
+    /**
+     * Launches Lens from an entry point.
+     * @param lensEntryPoint the Lens entry point.
+     */
+    void startLens(@LensEntryPoint int lensEntryPoint);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
index 3f8f5e2..7fa5c2693 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
@@ -378,13 +378,14 @@
         mContextMenuManager = new ContextMenuManager(mNewTabPageManager.getNavigationDelegate(),
                 mFeedSurfaceProvider.getTouchEnabledDelegate(), closeContextMenuCallback,
                 NewTabPage.CONTEXT_MENU_USER_ACTION_PREFIX);
-        mTab.getWindowAndroid().addContextMenuCloseListener(mContextMenuManager);
+        windowAndroid.addContextMenuCloseListener(mContextMenuManager);
 
         mNewTabPageLayout.initialize(mNewTabPageManager, activity, mTileGroupDelegate,
                 mSearchProviderHasLogo,
                 TemplateUrlServiceFactory.get().isDefaultSearchEngineGoogle(),
                 mFeedSurfaceProvider.getScrollDelegate(), mContextMenuManager,
-                mFeedSurfaceProvider.getUiConfig(), activityTabProvider, lifecycleDispatcher, uma);
+                mFeedSurfaceProvider.getUiConfig(), activityTabProvider, lifecycleDispatcher, uma,
+                mTab.isIncognito(), windowAndroid);
         TraceEvent.end(TAG);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
index dbe079a..c4b4f8e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
@@ -35,6 +35,7 @@
 import org.chromium.chrome.browser.explore_sites.ExperimentalExploreSitesSection;
 import org.chromium.chrome.browser.explore_sites.ExploreSitesBridge;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
+import org.chromium.chrome.browser.lens.LensEntryPoint;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.native_page.ContextMenuManager;
 import org.chromium.chrome.browser.ntp.LogoBridge.Logo;
@@ -64,6 +65,7 @@
 import org.chromium.components.browser_ui.widget.displaystyle.UiConfig;
 import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter;
 import org.chromium.ui.base.DeviceFormFactor;
+import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.vr.VrModeObserver;
 
 /**
@@ -129,6 +131,8 @@
     /** Flag used to request some layout changes after the next layout pass is completed. */
     private boolean mTileCountChanged;
     private boolean mSnapshotTileGridChanged;
+    private boolean mIsIncognito;
+    private WindowAndroid mWindowAndroid;
 
     /**
      * Vertical inset to add to the top and bottom of the search box bounds. May be 0 if no inset
@@ -221,18 +225,23 @@
      * @param tabProvider Provides the current active tab.
      * @param lifecycleDispatcher Activity lifecycle dispatcher.
      * @param uma {@link NewTabPageUma} object recording user metrics.
+     * @param isIncognito Whether the new tab page is in incognito mode.
+     * @param windowAndroid An instance of a {@link WindowAndroid}
      */
     public void initialize(NewTabPageManager manager, Activity activity,
             TileGroup.Delegate tileGroupDelegate, boolean searchProviderHasLogo,
             boolean searchProviderIsGoogle, ScrollDelegate scrollDelegate,
             ContextMenuManager contextMenuManager, UiConfig uiConfig, Supplier<Tab> tabProvider,
-            ActivityLifecycleDispatcher lifecycleDispatcher, NewTabPageUma uma) {
+            ActivityLifecycleDispatcher lifecycleDispatcher, NewTabPageUma uma, boolean isIncognito,
+            WindowAndroid windowAndroid) {
         TraceEvent.begin(TAG + ".initialize()");
         mScrollDelegate = scrollDelegate;
         mManager = manager;
         mActivity = activity;
         mUiConfig = uiConfig;
         mNewTabPageUma = uma;
+        mIsIncognito = isIncognito;
+        mWindowAndroid = windowAndroid;
 
         Profile profile = Profile.getLastUsedRegularProfile();
         OfflinePageBridge offlinePageBridge =
@@ -263,7 +272,7 @@
                 mManager.getNavigationDelegate(), mSearchProviderLogoView, profile);
 
         mSearchBoxCoordinator = new SearchBoxCoordinator(getContext(), this);
-        mSearchBoxCoordinator.initialize(lifecycleDispatcher);
+        mSearchBoxCoordinator.initialize(lifecycleDispatcher, mIsIncognito, mWindowAndroid);
         if (!DeviceFormFactor.isNonMultiDisplayContextOnTablet(activity)) {
             mSearchBoxBoundsVerticalInset = getResources().getDimensionPixelSize(
                     R.dimen.ntp_search_box_bounds_vertical_inset_modern);
@@ -272,6 +281,7 @@
 
         initializeSearchBoxTextView();
         initializeVoiceSearchButton();
+        initializeLensButton();
         initializeLayoutChangeListener();
         setSearchProviderInfo(searchProviderHasLogo, searchProviderIsGoogle);
         mSearchProviderLogoView.showSearchProviderInitialView();
@@ -333,7 +343,7 @@
             // View is 48dp, image is 24dp. Increasing the padding from 4dp -> 8dp will split the
             // remaining 16dp evenly between start/end resulting in a paddingEnd of 8dp.
             int paddingStart = getResources().getDimensionPixelSize(
-                    R.dimen.sei_ntp_voice_button_start_padding);
+                    R.dimen.sei_ntp_fakebox_button_start_padding);
             ImageView voiceSearchButton = findViewById(R.id.voice_search_button);
             voiceSearchButton.setPaddingRelative(paddingStart, voiceSearchButton.getPaddingTop(),
                     getPaddingEnd(), voiceSearchButton.getPaddingBottom());
@@ -342,6 +352,24 @@
         TraceEvent.end(TAG + ".initializeVoiceSearchButton()");
     }
 
+    private void initializeLensButton() {
+        TraceEvent.begin(TAG + ".initializeLensButton()");
+        // TODO(b/181067692): Report user action for this click.
+        mSearchBoxCoordinator.addLensButtonClickListener(
+                v -> mSearchBoxCoordinator.startLens(LensEntryPoint.NEW_TAB_PAGE));
+        if (SearchEngineLogoUtils.getInstance().isSearchEngineLogoEnabled()) {
+            // View is 48dp, image is 24dp. Increasing the padding from 4dp -> 8dp will split the
+            // remaining 16dp evenly between start/end resulting in a paddingEnd of 8dp.
+            int paddingStart = getResources().getDimensionPixelSize(
+                    R.dimen.sei_ntp_fakebox_button_start_padding);
+            ImageView lensButton = findViewById(R.id.lens_camera_button);
+            lensButton.setPaddingRelative(paddingStart, lensButton.getPaddingTop(), getPaddingEnd(),
+                    lensButton.getPaddingBottom());
+        }
+
+        TraceEvent.end(TAG + ".initializeLensButton()");
+    }
+
     private void initializeLayoutChangeListener() {
         TraceEvent.begin(TAG + ".initializeLayoutChangeListener()");
         addOnLayoutChangeListener(
@@ -745,7 +773,29 @@
         View searchBoxContainerView = mSearchBoxCoordinator.getView();
         searchBoxContainerView.setPadding(searchBoxContainerView.getPaddingStart(),
                 searchBoxContainerView.getPaddingTop(),
-                mManager.isVoiceSearchEnabled() ? 0 : mSearchBoxEndPadding,
+                mManager.isVoiceSearchEnabled()
+                                || mSearchBoxCoordinator.isLensEnabled(LensEntryPoint.NEW_TAB_PAGE)
+                        ? 0
+                        : mSearchBoxEndPadding,
+                searchBoxContainerView.getPaddingBottom());
+    }
+
+    /**
+     * Update the visibility of the Lens button based on whether the feature is currently
+     * enabled.
+     */
+    void updateLensButtonVisibility() {
+        if (mSearchBoxEndPadding == UNSET_RESOURCE_FLAG) {
+            mSearchBoxEndPadding = SearchEngineLogoUtils.getInstance().isSearchEngineLogoEnabled()
+                    ? getResources().getDimensionPixelSize(R.dimen.sei_search_box_lateral_padding)
+                    : getResources().getDimensionPixelSize(R.dimen.location_bar_lateral_padding);
+        }
+        boolean isLensEnabled = mSearchBoxCoordinator.isLensEnabled(LensEntryPoint.NEW_TAB_PAGE);
+        mSearchBoxCoordinator.setLensButtonVisibility(isLensEnabled);
+        View searchBoxContainerView = mSearchBoxCoordinator.getView();
+        searchBoxContainerView.setPadding(searchBoxContainerView.getPaddingStart(),
+                searchBoxContainerView.getPaddingTop(),
+                isLensEnabled || mManager.isVoiceSearchEnabled() ? 0 : mSearchBoxEndPadding,
                 searchBoxContainerView.getPaddingBottom());
     }
 
@@ -762,6 +812,7 @@
 
         if (visibility == VISIBLE) {
             updateVoiceSearchButtonVisibility();
+            updateLensButtonVisibility();
             maybeShowVideoTutorialTryNowIPH();
         }
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/search/SearchBoxCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/search/SearchBoxCoordinator.java
index 2143c5e8..83233aa 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/search/SearchBoxCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/search/SearchBoxCoordinator.java
@@ -13,7 +13,9 @@
 import android.view.ViewGroup;
 
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.lens.LensEntryPoint;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
+import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modelutil.PropertyModel;
 
 /**
@@ -25,6 +27,8 @@
     private final PropertyModel mModel;
     private final ViewGroup mView;
     private final SearchBoxMediator mMediator;
+    private boolean mIsIncognito;
+    private WindowAndroid mWindowAndroid;
 
     /** Constructor. */
     public SearchBoxCoordinator(Context context, ViewGroup parent) {
@@ -33,8 +37,11 @@
         mMediator = new SearchBoxMediator(context, mModel, mView);
     }
 
-    public void initialize(ActivityLifecycleDispatcher activityLifecycleDispatcher) {
+    public void initialize(ActivityLifecycleDispatcher activityLifecycleDispatcher,
+            boolean isIncognito, WindowAndroid windowAndroid) {
         mMediator.initialize(activityLifecycleDispatcher);
+        mIsIncognito = isIncognito;
+        mWindowAndroid = windowAndroid;
     }
 
     public View getView() {
@@ -85,6 +92,14 @@
         mMediator.addVoiceSearchButtonClickListener(listener);
     }
 
+    public void setLensButtonVisibility(boolean visible) {
+        mModel.set(SearchBoxProperties.LENS_VISIBILITY, visible);
+    }
+
+    public void addLensButtonClickListener(OnClickListener listener) {
+        mMediator.addLensButtonClickListener(listener);
+    }
+
     public void setChipText(String chipText) {
         mMediator.setChipText(chipText);
     }
@@ -97,4 +112,16 @@
         Pair<String, Boolean> searchText = mModel.get(SearchBoxProperties.SEARCH_TEXT);
         return searchText == null ? false : searchText.second;
     }
+
+    public boolean isLensEnabled(@LensEntryPoint int lensEntryPoint) {
+        return mMediator.isLensEnabled(lensEntryPoint, mIsIncognito);
+    }
+
+    public void startLens(@LensEntryPoint int lensEntryPoint) {
+        mMediator.startLens(lensEntryPoint, mWindowAndroid, mIsIncognito);
+    }
+
+    public void setIncognitoMode(boolean isIncognito) {
+        mIsIncognito = isIncognito;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/search/SearchBoxMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/search/SearchBoxMediator.java
index a63023d..1276dbf2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/search/SearchBoxMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/search/SearchBoxMediator.java
@@ -17,6 +17,10 @@
 import androidx.core.graphics.drawable.RoundedBitmapDrawable;
 
 import org.chromium.chrome.browser.gsa.GSAState;
+import org.chromium.chrome.browser.lens.LensController;
+import org.chromium.chrome.browser.lens.LensEntryPoint;
+import org.chromium.chrome.browser.lens.LensIntentParams;
+import org.chromium.chrome.browser.lens.LensQueryParams;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.lifecycle.Destroyable;
 import org.chromium.chrome.browser.lifecycle.NativeInitObserver;
@@ -26,6 +30,7 @@
 import org.chromium.components.browser_ui.styles.ChromeColors;
 import org.chromium.components.externalauth.ExternalAuthUtils;
 import org.chromium.ui.base.ViewUtils;
+import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
@@ -38,6 +43,7 @@
     private final PropertyModel mModel;
     private final ViewGroup mView;
     private final List<OnClickListener> mVoiceSearchClickListeners = new ArrayList<>();
+    private final List<OnClickListener> mLensClickListeners = new ArrayList<>();
     private ActivityLifecycleDispatcher mActivityLifecycleDispatcher;
     private AssistantVoiceSearchService mAssistantVoiceSearchService;
     private SearchBoxChipDelegate mChipDelegate;
@@ -129,6 +135,20 @@
     }
 
     /**
+     * Called to add a click listener for the voice search button.
+     */
+    void addLensButtonClickListener(OnClickListener listener) {
+        boolean hasExistingListeners = !mLensClickListeners.isEmpty();
+        mLensClickListeners.add(listener);
+        if (hasExistingListeners) return;
+        mModel.set(SearchBoxProperties.LENS_CLICK_CALLBACK, v -> {
+            for (OnClickListener clickListener : mLensClickListeners) {
+                clickListener.onClick(v);
+            }
+        });
+    }
+
+    /**
      * Called to set or clear a chip on the search box.
      * @param chipText The text to be shown on the chip.
      */
@@ -152,6 +172,29 @@
         });
     }
 
+    /**
+     * Launch the Lens app.
+     * @param lensEntryPoint A {@link LensEntryPoint}.
+     * @param windowAndroid A {@link WindowAndroid} instance.
+     * @param isIncognito Whether the request is from a Incognito tab.
+     */
+    void startLens(
+            @LensEntryPoint int lensEntryPoint, WindowAndroid windowAndroid, boolean isIncognito) {
+        LensController.getInstance().startLens(
+                windowAndroid, new LensIntentParams.Builder(lensEntryPoint, isIncognito).build());
+    }
+
+    /**
+     * Check whether the Lens is enabled for an entry point.
+     * @param lensEntryPoint A {@link LensEntryPoint}.
+     * @param isIncognito Whether the request is from a Incognito tab.
+     * @return Whether the Lens is currently enabled.
+     */
+    boolean isLensEnabled(@LensEntryPoint int lensEntryPoint, boolean isIncognito) {
+        return LensController.getInstance().isLensEnabled(
+                new LensQueryParams.Builder(lensEntryPoint, isIncognito).build());
+    }
+
     private Drawable getRoundedDrawable(Bitmap bitmap) {
         if (bitmap == null) return null;
         RoundedBitmapDrawable roundedBitmapDrawable =
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/search/SearchBoxProperties.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/search/SearchBoxProperties.java
index 087831e..f732779 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/search/SearchBoxProperties.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/search/SearchBoxProperties.java
@@ -29,6 +29,9 @@
             new WritableObjectPropertyKey<>();
     WritableObjectPropertyKey<OnClickListener> VOICE_SEARCH_CLICK_CALLBACK =
             new WritableObjectPropertyKey<>();
+    WritableBooleanPropertyKey LENS_VISIBILITY = new WritableBooleanPropertyKey();
+    WritableObjectPropertyKey<OnClickListener> LENS_CLICK_CALLBACK =
+            new WritableObjectPropertyKey<>();
     WritableObjectPropertyKey<Pair<String, Boolean>> SEARCH_TEXT =
             new WritableObjectPropertyKey<>();
     WritableBooleanPropertyKey SEARCH_HINT_VISIBILITY = new WritableBooleanPropertyKey();
@@ -47,7 +50,8 @@
 
     PropertyKey[] ALL_KEYS = new PropertyKey[] {ALPHA, BACKGROUND, VISIBILITY,
             VOICE_SEARCH_VISIBILITY, VOICE_SEARCH_DRAWABLE, VOICE_SEARCH_COLOR_STATE_LIST,
-            VOICE_SEARCH_CLICK_CALLBACK, SEARCH_TEXT, SEARCH_HINT_VISIBILITY,
-            SEARCH_BOX_CLICK_CALLBACK, SEARCH_BOX_TEXT_WATCHER, SEARCH_BOX_HINT_COLOR, CHIP_TEXT,
-            CHIP_VISIBILITY, CHIP_DRAWABLE, CHIP_CLICK_CALLBACK, CHIP_CANCEL_CALLBACK};
+            VOICE_SEARCH_CLICK_CALLBACK, LENS_VISIBILITY, LENS_CLICK_CALLBACK, SEARCH_TEXT,
+            SEARCH_HINT_VISIBILITY, SEARCH_BOX_CLICK_CALLBACK, SEARCH_BOX_TEXT_WATCHER,
+            SEARCH_BOX_HINT_COLOR, CHIP_TEXT, CHIP_VISIBILITY, CHIP_DRAWABLE, CHIP_CLICK_CALLBACK,
+            CHIP_CANCEL_CALLBACK};
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/search/SearchBoxViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/search/SearchBoxViewBinder.java
index 56221de3..ba352c2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/search/SearchBoxViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/search/SearchBoxViewBinder.java
@@ -25,6 +25,7 @@
     public final void bind(PropertyModel model, View view, PropertyKey propertyKey) {
         ImageView voiceSearchButton =
                 view.findViewById(org.chromium.chrome.R.id.voice_search_button);
+        ImageView lensButton = view.findViewById(org.chromium.chrome.R.id.lens_camera_button);
         View searchBoxContainer = view;
         final TextView searchBoxTextView = searchBoxContainer.findViewById(R.id.search_box_text);
         final ChipView chipView = searchBoxContainer.findViewById(R.id.query_tiles_chip);
@@ -49,6 +50,11 @@
             voiceSearchButton.setVisibility(model.get(SearchBoxProperties.VOICE_SEARCH_VISIBILITY)
                             ? View.VISIBLE
                             : View.GONE);
+        } else if (SearchBoxProperties.LENS_VISIBILITY == propertyKey) {
+            lensButton.setVisibility(
+                    model.get(SearchBoxProperties.LENS_VISIBILITY) ? View.VISIBLE : View.GONE);
+        } else if (SearchBoxProperties.LENS_CLICK_CALLBACK == propertyKey) {
+            lensButton.setOnClickListener(model.get(SearchBoxProperties.LENS_CLICK_CALLBACK));
         } else if (SearchBoxProperties.SEARCH_BOX_CLICK_CALLBACK == propertyKey) {
             searchBoxTextView.setOnClickListener(
                     model.get(SearchBoxProperties.SEARCH_BOX_CLICK_CALLBACK));
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/DEPS b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/DEPS
index 8747145c..1807816 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/DEPS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/DEPS
@@ -14,6 +14,10 @@
   "+chrome/android/java/src/org/chromium/chrome/browser/gsa/GSAState.java",
   "+chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java",
   "+chrome/android/java/src/org/chromium/chrome/browser/locale/LocaleManager.java",
+  "+chrome/android/java/src/org/chromium/chrome/browser/lens/LensController.java",
+  "+chrome/android/java/src/org/chromium/chrome/browser/lens/LensEntryPoint.java",
+  "+chrome/android/java/src/org/chromium/chrome/browser/lens/LensIntentParams.java",
+  "+chrome/android/java/src/org/chromium/chrome/browser/lens/LensQueryParams.java",
   "+chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java",
   "+chrome/android/java/src/org/chromium/chrome/browser/night_mode/NightModeUtils.java",
   "+chrome/android/java/src/org/chromium/chrome/browser/ntp/FakeboxDelegate.java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
index 499c3f4..fd21c566 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
@@ -76,6 +76,7 @@
     private View mUrlBar;
     private View mDeleteButton;
     private View mMicButton;
+    private View mLensButton;
     private final OneshotSupplierImpl<TemplateUrlService> mTemplateUrlServiceSupplier =
             new OneshotSupplierImpl<>();
     private CallbackController mCallbackController = new CallbackController();
@@ -157,6 +158,9 @@
         mMicButton = mLocationBarLayout.findViewById(R.id.mic_button);
         mMicButton.setOnClickListener(mLocationBarMediator::micButtonClicked);
 
+        mLensButton = mLocationBarLayout.findViewById(R.id.lens_camera_button);
+        mLensButton.setOnClickListener(mLocationBarMediator::lensButtonClicked);
+
         mUrlBar.setOnKeyListener(mLocationBarMediator);
         mUrlCoordinator.addUrlTextChangeListener(mAutocompleteCoordinator);
         // The LocationBar's direction is tied to the UrlBar's text direction. Icons inside the
@@ -201,6 +205,9 @@
         mMicButton.setOnClickListener(null);
         mMicButton = null;
 
+        mLensButton.setOnClickListener(null);
+        mLensButton = null;
+
         mLocationBarMediator.removeUrlFocusChangeListener(mUrlCoordinator);
         mUrlCoordinator.destroy();
         mUrlCoordinator = null;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
index 81d8a156..29532d7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
@@ -39,6 +39,7 @@
 public class LocationBarLayout extends FrameLayout {
     protected ImageButton mDeleteButton;
     protected ImageButton mMicButton;
+    protected ImageButton mLensButton;
     protected UrlBar mUrlBar;
 
     protected UrlBarCoordinator mUrlCoordinator;
@@ -70,6 +71,7 @@
         mDeleteButton = findViewById(R.id.delete_button);
         mUrlBar = findViewById(R.id.url_bar);
         mMicButton = findViewById(R.id.mic_button);
+        mLensButton = findViewById(R.id.lens_camera_button);
         mUrlActionContainer = (LinearLayout) findViewById(R.id.url_action_container);
     }
 
@@ -287,6 +289,11 @@
         mMicButton.setVisibility(shouldShow ? VISIBLE : GONE);
     }
 
+    /** Sets the visibility of the mic button. */
+    /* package */ void setLensButtonVisibility(boolean shouldShow) {
+        mLensButton.setVisibility(shouldShow ? VISIBLE : GONE);
+    }
+
     protected void setUnfocusedWidth(int unfocusedWidth) {
         mStatusCoordinator.setUnfocusedLocationBarWidth(unfocusedWidth);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
index 1bf7392..8808f804 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
@@ -34,6 +34,10 @@
 import org.chromium.chrome.browser.download.DownloadUtils;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.gsa.GSAState;
+import org.chromium.chrome.browser.lens.LensController;
+import org.chromium.chrome.browser.lens.LensEntryPoint;
+import org.chromium.chrome.browser.lens.LensIntentParams;
+import org.chromium.chrome.browser.lens.LensQueryParams;
 import org.chromium.chrome.browser.locale.LocaleManager;
 import org.chromium.chrome.browser.ntp.FakeboxDelegate;
 import org.chromium.chrome.browser.ntp.NewTabPageUma;
@@ -143,6 +147,7 @@
     private boolean mUrlFocusedWithoutAnimations;
     private boolean mIsUrlFocusChangeInProgress;
     private final boolean mIsTablet;
+    private boolean mShouldShowLensButtonWhenUnfocused;
     private boolean mShouldShowMicButtonWhenUnfocused;
     // Whether the microphone and bookmark buttons should be shown in the tablet location bar. These
     // buttons are hidden if the window size is < 600dp.
@@ -285,6 +290,7 @@
         }
         mDeferredNativeRunnables.clear();
         updateMicButtonState();
+        updateLensButtonState();
     }
 
     /*package */ void setUrlFocusChangeFraction(float fraction) {
@@ -474,6 +480,7 @@
     /* package */ void updateButtonVisibility() {
         updateDeleteButtonVisibility();
         updateMicButtonVisibility();
+        updateLensButtonVisibility();
         if (mIsTablet) {
             updateTabletButtonsVisibility();
         }
@@ -527,6 +534,12 @@
                 VoiceRecognitionHandler.VoiceInteractionSource.OMNIBOX);
     }
 
+    /** package */ void lensButtonClicked(View view) {
+        if (!mNativeInitialized || mLocationBarDataProvider == null) return;
+        RecordUserAction.record("MobileOmniboxLens");
+        startLens(LensEntryPoint.OMNIBOX);
+    }
+
     /* package */ void setUrlFocusChangeInProgress(boolean inProgress) {
         if (mUrlCoordinator == null) return;
         mIsUrlFocusChangeInProgress = inProgress;
@@ -630,6 +643,11 @@
         mShouldShowMicButtonWhenUnfocused = shouldShow;
     }
 
+    /* package */ void setShouldShowLensButtonWhenUnfocusedForPhone(boolean shouldShow) {
+        assert !mIsTablet;
+        mShouldShowLensButtonWhenUnfocused = shouldShow;
+    }
+
     /**
      * @param shouldShow Whether buttons should be displayed in the URL bar when it's not
      *                          focused.
@@ -907,6 +925,10 @@
         updateButtonVisibility();
     }
 
+    private void updateLensButtonState() {
+        updateButtonVisibility();
+    }
+
     /**
      * Updates the display of the mic button.
      */
@@ -914,6 +936,10 @@
         mLocationBarLayout.setMicButtonVisibility(shouldShowMicButton());
     }
 
+    private void updateLensButtonVisibility() {
+        mLocationBarLayout.setLensButtonVisibility(shouldShowLensButton());
+    }
+
     private void updateDeleteButtonVisibility() {
         mLocationBarLayout.setDeleteButtonVisibility(shouldShowDeleteButton());
     }
@@ -955,6 +981,21 @@
         }
     }
 
+    private boolean shouldShowLensButton() {
+        if (mIsTablet) {
+            // TODO(b/180967835): add logic to enable Lens for tablets.
+            return false;
+        } else if (mShouldShowButtonsWhenUnfocused) {
+            return isLensEnabled(LensEntryPoint.OMNIBOX) && mNativeInitialized
+                    && (mUrlHasFocus || mIsUrlFocusChangeInProgress);
+        } else {
+            boolean deleteButtonVisible = shouldShowDeleteButton();
+            return isLensEnabled(LensEntryPoint.OMNIBOX) && !deleteButtonVisible
+                    && (mUrlHasFocus || mIsUrlFocusChangeInProgress || mUrlFocusChangeFraction > 0f
+                            || mShouldShowMicButtonWhenUnfocused);
+        }
+    }
+
     private boolean shouldShowSaveOfflineButton() {
         assert mIsTablet;
         if (!mNativeInitialized || mLocationBarDataProvider == null) return false;
@@ -1318,4 +1359,19 @@
         updateSearchEngineStatusIcon();
         mLocationBarLayout.updateStatusVisibility();
     }
+
+    @Override
+    public boolean isLensEnabled(@LensEntryPoint int lensEntryPoint) {
+        return LensController.getInstance().isLensEnabled(
+                new LensQueryParams.Builder(lensEntryPoint, mLocationBarDataProvider.isIncognito())
+                        .build());
+    }
+
+    @Override
+    public void startLens(@LensEntryPoint int lensEntryPoint) {
+        // TODO(b/181067692): Report user action for this click.
+        LensController.getInstance().startLens(mWindowAndroid,
+                new LensIntentParams.Builder(lensEntryPoint, mLocationBarDataProvider.isIncognito())
+                        .build());
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/query_tiles/QueryTileSection.java b/chrome/android/java/src/org/chromium/chrome/browser/query_tiles/QueryTileSection.java
index 8e598a2..93005bc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/query_tiles/QueryTileSection.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/query_tiles/QueryTileSection.java
@@ -95,6 +95,7 @@
                 ImageFetcherFactory.createImageFetcher(ImageFetcherConfig.IN_MEMORY_WITH_DISK_CACHE,
                         profile, GlobalDiscardableReferencePool.getReferencePool());
         mSearchBoxCoordinator.addVoiceSearchButtonClickListener(v -> reloadTiles());
+        mSearchBoxCoordinator.addLensButtonClickListener(v -> reloadTiles());
         reloadTiles();
     }
 
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index a828e5d..998ca85 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -6050,6 +6050,11 @@
      flag_descriptions::kContextMenuTranslateWithGoogleLensDescription,
      kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kContextMenuTranslateWithGoogleLens)},
+
+    {"lens-camera-assisted-search",
+     flag_descriptions::kLensCameraAssistedSearchName,
+     flag_descriptions::kLensCameraAssistedSearchDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(chrome::android::kLensCameraAssistedSearch)},
 #endif  // defined(OS_ANDROID)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 4a1cc02f..6af3533 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -3324,6 +3324,11 @@
     "expiry_milestone": 92
   },
   {
+    "name": "lens-camera-assisted-search",
+    "owners": [ "yusuyoutube", "benwgold", "fgorski", "wylieb", "lens-chrome" ],
+    "expiry_milestone": 94
+  },
+  {
     "name": "list-all-display-modes",
     "owners": [ "//ui/display/OWNERS" ],
     // This flag is used for debugging and development purposes to list all
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 485e9b88..34e1e03 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1417,6 +1417,12 @@
     "Show security warnings for sites that use legacy TLS versions (TLS 1.0 "
     "and TLS 1.1), which are deprecated and will be removed in the future.";
 
+const char kLensCameraAssistedSearchName[] =
+    "Google Lens in Omnibox and New Tab Page";
+const char kLensCameraAssistedSearchDescription[] =
+    "Enable an entry point to Google Lens to allow users to search what they "
+    "see using their mobile camera.";
+
 const char kLiteVideoName[] = "Enable LiteVideos";
 const char kLiteVideoDescription[] =
     "Enable the LiteVideo optimization to throttle media requests to "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 9214f9a6..3cb81079 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -838,6 +838,9 @@
 extern const char kLegacyTLSWarningsName[];
 extern const char kLegacyTLSWarningsDescription[];
 
+extern const char kLensCameraAssistedSearchName[];
+extern const char kLensCameraAssistedSearchDescription[];
+
 extern const char kLiteVideoName[];
 extern const char kLiteVideoDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 39cd6b0..059c60d 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -196,6 +196,7 @@
     &kInlineUpdateFlow,
     &kInstantStart,
     &kKitKatSupported,
+    &kLensCameraAssistedSearch,
     &kNotificationSuspender,
     &kOfflineIndicatorV2,
     &kOfflineMeasurementsBackgroundTask,
@@ -460,6 +461,9 @@
 const base::Feature kContextMenuTranslateWithGoogleLens{
     "ContextMenuTranslateWithGoogleLens", base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kLensCameraAssistedSearch{
+    "LensCameraAssistedSearch", base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kContextualSearchDebug{"ContextualSearchDebug",
                                            base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 98f60771..64d7531 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -91,6 +91,7 @@
 extern const base::Feature kInstantStart;
 extern const base::Feature kKitKatSupported;
 extern const base::Feature kLanguagesPreference;
+extern const base::Feature kLensCameraAssistedSearch;
 extern const base::Feature kNotificationSuspender;
 extern const base::Feature kOfflineIndicatorV2;
 extern const base::Feature kOfflineMeasurementsBackgroundTask;
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index 26b0a43..7999501f 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -274,6 +274,7 @@
             "ContextMenuSearchAndShopWithGoogleLens";
     public static final String CONTEXT_MENU_TRANSLATE_WITH_GOOGLE_LENS =
             "ContextMenuTranslateWithGoogleLens";
+    public static final String LENS_CAMERA_ASSISTED_SEARCH = "LensCameraAssistedSearch";
     public static final String CONTEXTUAL_SEARCH_DEBUG = "ContextualSearchDebug";
     public static final String CONTEXTUAL_SEARCH_DEFINITIONS = "ContextualSearchDefinitions";
     public static final String CONTEXTUAL_SEARCH_LEGACY_HTTP_POLICY =
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 39f057c..e8c25fd3 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -3269,6 +3269,9 @@
       <message name="IDS_ACCESSIBILITY_TOOLBAR_BTN_MIC" desc="Content description for the voice search button.">
         Start voice search
       </message>
+      <message name="IDS_ACCESSIBILITY_BTN_LENS_CAMERA" desc="Content description for the Lens button. When the user taps the button, they can perform a 'search with your camera or photos', which opens the Google Lens app.">
+        Search with your camera using Google Lens
+      </message>
       <message name="IDS_ACCESSIBILITY_NEW_TAB_PAGE" desc="Accessibility text to read aloud when the user focuses the new tab view.">
         New tab
       </message>
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ACCESSIBILITY_BTN_LENS_CAMERA.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ACCESSIBILITY_BTN_LENS_CAMERA.png.sha1
new file mode 100644
index 0000000..7817a62
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ACCESSIBILITY_BTN_LENS_CAMERA.png.sha1
@@ -0,0 +1 @@
+b437a8116be72e934e4ec1b351e9c9cd3f339814
\ No newline at end of file
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 90d2722..fb04dfe 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -46372,6 +46372,7 @@
   <int value="799680074" label="ContextualSearchTranslationModel:enabled"/>
   <int value="802463708" label="WebViewSurfaceControl:enabled"/>
   <int value="803282885" label="PreferHtmlOverPlugins:disabled"/>
+  <int value="804958673" label="LensCameraAssistedSearch:enabled"/>
   <int value="805882800" label="SafetyCheckChromeCleanerChild:disabled"/>
   <int value="806035639" label="EnableNeuralPalmDetectionFilter:disabled"/>
   <int value="806334184" label="AndroidSpellChecker:enabled"/>
@@ -47074,6 +47075,7 @@
   <int value="1454006695" label="BlinkHeapUnifiedGarbageCollection:disabled"/>
   <int value="1454028829" label="EnhancedProtectionPromoCard:disabled"/>
   <int value="1454143461" label="CaptureThumbnailOnNavigatingAway:disabled"/>
+  <int value="1454295160" label="LensCameraAssistedSearch:disabled"/>
   <int value="1454363479" label="disable-storage-manager"/>
   <int value="1454527518" label="ArcNativeBridgeExperiment:enabled"/>
   <int value="1455881930" label="V8VmFuture:enabled"/>