[UW] Add high-level layout for instance switcher v2

Screenshot: https://screenshot.googleplex.com/7RdUiLtDzrdZSpy

Note: This update is flag-guarded and will be done in a series of CLs.

Bug: 414687867
Change-Id: Ia476169f3d3bde03da5623e3b57171de9a6345bd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6520037
Reviewed-by: Jinsuk Kim <[email protected]>
Commit-Queue: Aishwarya Rajesh <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1458137}
diff --git a/chrome/browser/ui/android/multiwindow/BUILD.gn b/chrome/browser/ui/android/multiwindow/BUILD.gn
index 38f1a4f..dcf4a2f 100644
--- a/chrome/browser/ui/android/multiwindow/BUILD.gn
+++ b/chrome/browser/ui/android/multiwindow/BUILD.gn
@@ -37,10 +37,13 @@
     "//components/browser_ui/widget/android:java",
     "//components/favicon/android:java",
     "//components/feature_engagement/public:public_java",
+    "//third_party/android_deps:material_design_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
     "//third_party/androidx:androidx_appcompat_appcompat_resources_java",
     "//third_party/androidx:androidx_core_core_java",
+    "//third_party/androidx:androidx_recyclerview_recyclerview_java__classes",
     "//ui/android:ui_no_recycler_view_java",
+    "//ui/android:ui_recycler_view_java",
     "//ui/android:ui_utils_java",
     "//url:gurl_java",
   ]
@@ -52,8 +55,10 @@
     "java/res/drawable/checkmark_24dp.xml",
     "java/res/drawable/circle_green.xml",
     "java/res/layout/close_confirmation_dialog.xml",
+    "java/res/layout/instance_switcher_active_list.xml",
     "java/res/layout/instance_switcher_cmd_item.xml",
     "java/res/layout/instance_switcher_dialog.xml",
+    "java/res/layout/instance_switcher_dialog_v2.xml",
     "java/res/layout/instance_switcher_item.xml",
     "java/res/layout/instance_switcher_list.xml",
     "java/res/layout/target_selector_dialog.xml",
diff --git a/chrome/browser/ui/android/multiwindow/java/res/layout/instance_switcher_active_list.xml b/chrome/browser/ui/android/multiwindow/java/res/layout/instance_switcher_active_list.xml
new file mode 100644
index 0000000..1e90466
--- /dev/null
+++ b/chrome/browser/ui/android/multiwindow/java/res/layout/instance_switcher_active_list.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2025 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+       xmlns:app="http://schemas.android.com/apk/res-auto">
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/active_instance_list"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:divider="@null"/>
+</merge>
diff --git a/chrome/browser/ui/android/multiwindow/java/res/layout/instance_switcher_dialog_v2.xml b/chrome/browser/ui/android/multiwindow/java/res/layout/instance_switcher_dialog_v2.xml
new file mode 100644
index 0000000..252ba321
--- /dev/null
+++ b/chrome/browser/ui/android/multiwindow/java/res/layout/instance_switcher_dialog_v2.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2025 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingHorizontal="@dimen/instance_switcher_dialog_content_padding"
+    android:paddingTop="@dimen/instance_switcher_dialog_content_padding">
+
+    <com.google.android.material.tabs.TabLayout
+        android:id="@+id/tabs"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingBottom="@dimen/instance_switcher_dialog_list_item_padding"
+        style="@style/TabLayoutStyle">
+        <com.google.android.material.tabs.TabItem
+            android:id="@+id/active_instances"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+
+        <com.google.android.material.tabs.TabItem
+            android:id="@+id/inactive_instances"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+    </com.google.android.material.tabs.TabLayout>
+
+    <FrameLayout
+        android:id="@+id/instance_list_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <include layout="@layout/instance_switcher_active_list"/>
+    </FrameLayout>
+</LinearLayout>
diff --git a/chrome/browser/ui/android/multiwindow/java/res/values/dimens.xml b/chrome/browser/ui/android/multiwindow/java/res/values/dimens.xml
index 39079cf..71d16a6 100644
--- a/chrome/browser/ui/android/multiwindow/java/res/values/dimens.xml
+++ b/chrome/browser/ui/android/multiwindow/java/res/values/dimens.xml
@@ -8,4 +8,6 @@
 <resources xmlns:tools="http://schemas.android.com/tools">
     <dimen name="confirmation_dialog_width">328dp</dimen>
     <dimen name="confirmation_dialog_side_margin">16dp</dimen>
+    <dimen name="instance_switcher_dialog_content_padding">16dp</dimen>
+    <dimen name="instance_switcher_dialog_list_item_padding">2dp</dimen>
 </resources>
diff --git a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinator.java b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinator.java
index ce01788..655e3bd 100644
--- a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinator.java
+++ b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinator.java
@@ -19,11 +19,17 @@
 
 import androidx.annotation.IntDef;
 import androidx.annotation.VisibleForTesting;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.tabs.TabLayout;
+import com.google.android.material.tabs.TabLayout.Tab;
 
 import org.chromium.base.Callback;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
 import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
 import org.chromium.components.browser_ui.widget.BrowserUiListMenuUtils;
@@ -39,6 +45,7 @@
 import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
 import org.chromium.ui.modelutil.ModelListAdapter;
 import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.SimpleRecyclerViewAdapter;
 import org.chromium.ui.widget.Toast;
 
 import java.lang.annotation.Retention;
@@ -74,6 +81,7 @@
     private final ModelList mModelList = new ModelList();
     private final UiUtils mUiUtils;
     private final View mDialogView;
+    private @Nullable TabLayout mTabHeaderRow;
 
     private @Nullable PropertyModel mDialog;
     private @Nullable InstanceInfo mItemToDelete;
@@ -124,23 +132,51 @@
         mUiUtils = new UiUtils(mContext, iconBridge);
         mNewWindowAction = newWindowAction;
 
-        ModelListAdapter adapter = new ModelListAdapter(mModelList);
-        // TODO: Extend modern_list_item_view.xml to replace instance_switcher_item.xml
-        adapter.registerType(
-                EntryType.INSTANCE,
-                parentView ->
-                        LayoutInflater.from(mContext)
-                                .inflate(R.layout.instance_switcher_item, null),
-                InstanceSwitcherItemViewBinder::bind);
-        adapter.registerType(
-                EntryType.COMMAND,
-                parentView ->
-                        LayoutInflater.from(mContext)
-                                .inflate(R.layout.instance_switcher_cmd_item, null),
-                InstanceSwitcherItemViewBinder::bind);
-        mDialogView = LayoutInflater.from(context).inflate(R.layout.instance_switcher_dialog, null);
-        ListView listView = (ListView) mDialogView.findViewById(R.id.list_view);
-        listView.setAdapter(adapter);
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.INSTANCE_SWITCHER_V2)) {
+            var adapter = new SimpleRecyclerViewAdapter(mModelList);
+            adapter.registerType(
+                    EntryType.INSTANCE,
+                    parentView ->
+                            LayoutInflater.from(mContext)
+                                    .inflate(R.layout.instance_switcher_item, null),
+                    InstanceSwitcherItemViewBinder::bind);
+            adapter.registerType(
+                    EntryType.COMMAND,
+                    parentView ->
+                            LayoutInflater.from(mContext)
+                                    .inflate(R.layout.instance_switcher_cmd_item, null),
+                    InstanceSwitcherItemViewBinder::bind);
+
+            mDialogView =
+                    LayoutInflater.from(context)
+                            .inflate(R.layout.instance_switcher_dialog_v2, null);
+            mTabHeaderRow = mDialogView.findViewById(R.id.tabs);
+            View listContainer = mDialogView.findViewById(R.id.instance_list_container);
+            RecyclerView recyclerView = listContainer.findViewById(R.id.active_instance_list);
+            recyclerView.setLayoutManager(
+                    new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false));
+            recyclerView.setAdapter(adapter);
+        } else {
+            ModelListAdapter adapter = new ModelListAdapter(mModelList);
+            // TODO: Extend modern_list_item_view.xml to replace instance_switcher_item.xml
+            adapter.registerType(
+                    EntryType.INSTANCE,
+                    parentView ->
+                            LayoutInflater.from(mContext)
+                                    .inflate(R.layout.instance_switcher_item, null),
+                    InstanceSwitcherItemViewBinder::bind);
+            adapter.registerType(
+                    EntryType.COMMAND,
+                    parentView ->
+                            LayoutInflater.from(mContext)
+                                    .inflate(R.layout.instance_switcher_cmd_item, null),
+                    InstanceSwitcherItemViewBinder::bind);
+
+            mDialogView =
+                    LayoutInflater.from(context).inflate(R.layout.instance_switcher_dialog, null);
+            ListView listView = (ListView) mDialogView.findViewById(R.id.list_view);
+            listView.setAdapter(adapter);
+        }
     }
 
     private void show(List<InstanceInfo> items, boolean newWindowEnabled) {
@@ -153,6 +189,7 @@
         mNewWindowModel = new PropertyModel(InstanceSwitcherItemProperties.ALL_KEYS);
         enableNewWindowCommand(newWindowEnabled);
         mModelList.add(new ModelListAdapter.ListItem(EntryType.COMMAND, mNewWindowModel));
+        updateTabTitle(items.size(), items.size());
 
         mDialog = createDialog(mDialogView);
         mModalDialogManager.showDialog(mDialog, ModalDialogType.APP);
@@ -281,6 +318,9 @@
         RecordUserAction.record("Android.WindowManager.CloseWindow");
         // Removing an instance enables the new window item.
         enableNewWindowCommand(true);
+        // Number of instances is one less than the list size to exclude the new window item.
+        int numInstances = mModelList.size() - 1;
+        updateTabTitle(numInstances, numInstances);
     }
 
     private static boolean canSkipConfirm(InstanceInfo item) {
@@ -328,4 +368,16 @@
                 });
         dialog.show();
     }
+
+    private void updateTabTitle(int numActiveInstances, int numInactiveInstances) {
+        if (mTabHeaderRow == null) return;
+        Tab activeTab = mTabHeaderRow.getTabAt(0);
+        Tab inactiveTab = mTabHeaderRow.getTabAt(1);
+        assumeNonNull(activeTab);
+        assumeNonNull(inactiveTab);
+        activeTab.setText(
+                mContext.getString(R.string.instance_switcher_tabs_active, numActiveInstances));
+        inactiveTab.setText(
+                mContext.getString(R.string.instance_switcher_tabs_inactive, numInactiveInstances));
+    }
 }
diff --git a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinatorTest.java b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinatorTest.java
index c29d0c9..aafcc4b 100644
--- a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinatorTest.java
+++ b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinatorTest.java
@@ -8,6 +8,7 @@
 import static androidx.test.espresso.Espresso.onView;
 import static androidx.test.espresso.action.ViewActions.click;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition;
 import static androidx.test.espresso.matcher.RootMatchers.isDialog;
 import static androidx.test.espresso.matcher.RootMatchers.withDecorView;
 import static androidx.test.espresso.matcher.ViewMatchers.Visibility.GONE;
@@ -38,6 +39,9 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.Features.DisableFeatures;
+import org.chromium.base.test.util.Features.EnableFeatures;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
 import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
@@ -53,6 +57,7 @@
 /** Unit tests for {@link InstanceSwitcherCoordinator}. */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
+@DisableFeatures(ChromeFeatureList.INSTANCE_SWITCHER_V2)
 public class InstanceSwitcherCoordinatorTest {
     @Rule
     public BaseActivityTestRule<BlankUiTestActivity> mActivityTestRule =
@@ -125,6 +130,40 @@
 
     @Test
     @SmallTest
+    @EnableFeatures(ChromeFeatureList.INSTANCE_SWITCHER_V2)
+    public void testOpenWindow_InstanceSwitcherV2() throws Exception {
+        InstanceInfo[] instances =
+                new InstanceInfo[] {
+                    new InstanceInfo(
+                            0, 57, InstanceInfo.Type.CURRENT, "url0", "title0", 1, 0, false, 0),
+                    new InstanceInfo(
+                            1, 58, InstanceInfo.Type.OTHER, "ur11", "title1", 2, 0, false, 0),
+                    new InstanceInfo(
+                            2, 59, InstanceInfo.Type.OTHER, "url2", "title2", 0, 0, false, 0)
+                };
+        final CallbackHelper itemClickCallbackHelper = new CallbackHelper();
+        final int itemClickCount = itemClickCallbackHelper.getCallCount();
+        Callback<InstanceInfo> openCallback = (item) -> itemClickCallbackHelper.notifyCalled();
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    InstanceSwitcherCoordinator.showDialog(
+                            mActivityTestRule.getActivity(),
+                            mModalDialogManager,
+                            mIconBridge,
+                            openCallback,
+                            null,
+                            null,
+                            false,
+                            Arrays.asList(instances));
+                });
+        onView(withId(R.id.active_instance_list))
+                .inRoot(isDialog())
+                .perform(actionOnItemAtPosition(1, click()));
+        itemClickCallbackHelper.waitForCallback(itemClickCount);
+    }
+
+    @Test
+    @SmallTest
     public void testNewWindow() throws Exception {
         InstanceInfo[] instances =
                 new InstanceInfo[] {
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 67af9b59..bc3f230 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -5704,6 +5704,12 @@
       <message name="IDS_INSTANCE_SWITCHER_HEADER" desc="The header of multi-instance switcher dialog.">
         Manage windows
       </message>
+      <message name="IDS_INSTANCE_SWITCHER_TABS_ACTIVE" desc="The title of the active instances tab header on the multi-instance switcher dialog.">
+        Active (<ph name="ITEM_COUNT">%1$s<ex>2</ex></ph>)
+      </message>
+      <message name="IDS_INSTANCE_SWITCHER_TABS_INACTIVE" desc="The title of the inactive instances tab header on the multi-instance switcher dialog.">
+        Inactive (<ph name="ITEM_COUNT">%1$s<ex>2</ex></ph>)
+      </message>
       <message name="IDS_INSTANCE_SWITCHER_ENTRY_EMPTY_WINDOW" desc="Title of an instance entry that has no tabs in it.">
         Empty window
       </message>
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INSTANCE_SWITCHER_TABS_ACTIVE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INSTANCE_SWITCHER_TABS_ACTIVE.png.sha1
new file mode 100644
index 0000000..8f774c3
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INSTANCE_SWITCHER_TABS_ACTIVE.png.sha1
@@ -0,0 +1 @@
+5cbad4c960c257f0f4282adc18c9d11e70587c69
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INSTANCE_SWITCHER_TABS_INACTIVE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INSTANCE_SWITCHER_TABS_INACTIVE.png.sha1
new file mode 100644
index 0000000..8f774c3
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_INSTANCE_SWITCHER_TABS_INACTIVE.png.sha1
@@ -0,0 +1 @@
+5cbad4c960c257f0f4282adc18c9d11e70587c69
\ No newline at end of file