[PCCT] Add close button position customization

Bug: 1318601
Change-Id: I834d6fa4147e9194c7e5c7a8f3332291f3c0ba27
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3600795
Reviewed-by: Peter Conn <peconn@chromium.org>
Reviewed-by: Jinsuk Kim <jinsukkim@chromium.org>
Commit-Queue: Sinan Sahin <sinansahin@google.com>
Cr-Commit-Position: refs/heads/main@{#997362}
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index 48198f84..b4402d9d 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -269,7 +269,7 @@
     <!-- Custom Tabs dimensions -->
     <dimen name="custom_tabs_toolbar_maxWidth">60dp</dimen>
     <dimen name="custom_tabs_toolbar_vertical_padding">16dp</dimen>
-    <dimen name="custom_tabs_toolbar_horizontal_margin_no_close">8dp</dimen>
+    <dimen name="custom_tabs_toolbar_horizontal_margin_no_start">8dp</dimen>
     <dimen name="min_toolbar_icon_side_padding">6dp</dimen>
     <dimen name="custom_tabs_url_text_size">12sp</dimen>
     <dimen name="custom_tabs_title_text_size">16sp</dimen>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
index d958587..bde1c309 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
@@ -161,6 +161,7 @@
         CustomTabToolbar toolbar = mActivity.findViewById(R.id.toolbar);
         View coordinator = mActivity.findViewById(R.id.coordinator);
         mCustomTabHeightStrategy.onToolbarInitialized(coordinator, toolbar);
+        toolbar.setCloseButtonPosition(mIntentDataProvider.get().getCloseButtonPosition());
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
index 02b5adf1..8641b36 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
@@ -169,6 +169,13 @@
     public static final String EXTRA_INITIAL_ACTIVITY_HEIGHT_IN_PIXEL =
             "androidx.browser.customtabs.extra.INITIAL_ACTIVITY_HEIGHT_IN_PIXEL";
 
+    /**
+     * Extra that specifies the position of the close button on the toolbar. Default is
+     * {@link #CLOSE_BUTTON_POSITION_DEFAULT}.
+     */
+    public static final String EXTRA_CLOSE_BUTTON_POSITION =
+            "androidx.browser.customtabs.extra.CLOSE_BUTTON_POSITION";
+
     private static final String DEFAULT_POLICY_PARAM_NAME = "default_policy";
     private static final String DEFAULT_POLICY_USE_DENYLIST = "use-denylist";
     private static final String DEFAULT_POLICY_USE_ALLOWLIST = "use-allowlist";
@@ -850,4 +857,10 @@
         assert false : "We can't get here since the default policy is use denylist.";
         return false;
     }
+
+    @Override
+    public @CloseButtonPosition int getCloseButtonPosition() {
+        return IntentUtils.safeGetIntExtra(
+                mIntent, EXTRA_CLOSE_BUTTON_POSITION, CLOSE_BUTTON_POSITION_DEFAULT);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
index 282605e..862e13b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.customtabs.features.toolbar;
 
 import static org.chromium.base.MathUtils.interpolate;
+import static org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.CLOSE_BUTTON_POSITION_END;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -25,16 +26,19 @@
 import android.util.AttributeSet;
 import android.util.TypedValue;
 import android.view.ActionMode;
+import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.FrameLayout;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import androidx.annotation.ColorInt;
+import androidx.annotation.Dimension;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.appcompat.content.res.AppCompatResources;
@@ -44,6 +48,7 @@
 import org.chromium.base.supplier.Supplier;
 import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.CloseButtonPosition;
 import org.chromium.chrome.browser.crash.ChromePureJavaExceptionReporter;
 import org.chromium.chrome.browser.omnibox.LocationBar;
 import org.chromium.chrome.browser.omnibox.LocationBarDataProvider;
@@ -60,6 +65,7 @@
 import org.chromium.chrome.browser.theme.ThemeUtils;
 import org.chromium.chrome.browser.toolbar.LocationBarModel;
 import org.chromium.chrome.browser.toolbar.ToolbarProgressBar;
+import org.chromium.chrome.browser.toolbar.menu_button.MenuButton;
 import org.chromium.chrome.browser.toolbar.top.ToolbarLayout;
 import org.chromium.chrome.browser.toolbar.top.ToolbarPhone;
 import org.chromium.chrome.browser.ui.native_page.NativePage;
@@ -93,6 +99,7 @@
     private ImageView mIncognitoImageView;
     private LinearLayout mCustomActionButtons;
     private ImageButton mCloseButton;
+    private MenuButton mMenuButton;
     // This View will be non-null only for bottom sheet custom tabs.
     private ImageView mHandleView;
 
@@ -138,6 +145,7 @@
     }
 
     private HandleStrategy mHandleStrategy;
+    private @CloseButtonPosition int mCloseButtonPosition;
 
     /**
      * Constructor for getting this class inflated from an xml layout file.
@@ -159,6 +167,7 @@
         mCustomActionButtons = findViewById(R.id.action_buttons);
         mCloseButton = findViewById(R.id.close_button);
         mCloseButton.setOnLongClickListener(this);
+        mMenuButton = findViewById(R.id.menu_button_wrapper);
 
         mLocationBar.onFinishInflate(this);
     }
@@ -308,6 +317,14 @@
         mLocationBar.showBranding();
     }
 
+    /**
+     * Sets the close button position for this toolbar.
+     * @param closeButtonPosition The {@link CloseButtonPosition}.
+     */
+    public void setCloseButtonPosition(@CloseButtonPosition int closeButtonPosition) {
+        mCloseButtonPosition = closeButtonPosition;
+    }
+
     private void updateButtonsTint() {
         updateButtonTint(mCloseButton);
         int numCustomActionButtons = mCustomActionButtons.getChildCount();
@@ -324,11 +341,45 @@
         }
     }
 
+    private void maybeSwapCloseAndMenuButtons() {
+        if (mCloseButtonPosition != CLOSE_BUTTON_POSITION_END) return;
+
+        final View closeButton = findViewById(R.id.close_button);
+        final int closeButtonIndex = indexOfChild(closeButton);
+        final ViewGroup.LayoutParams closeButtonLayoutParams = closeButton.getLayoutParams();
+        final View menuButton = findViewById(R.id.menu_button_wrapper);
+        final int menuButtonIndex = indexOfChild(menuButton);
+        final ViewGroup.LayoutParams menuButtonLayoutParams = menuButton.getLayoutParams();
+        removeViewAt(menuButtonIndex);
+        addView(menuButton, closeButtonIndex, menuButtonLayoutParams);
+        removeView(closeButton);
+        addView(closeButton, menuButtonIndex, closeButtonLayoutParams);
+    }
+
+    private void maybeAdjustButtonSpacingForCloseButtonPosition() {
+        if (mCloseButtonPosition != CLOSE_BUTTON_POSITION_END) return;
+
+        final @Dimension int buttonWidth =
+                getResources().getDimensionPixelSize(R.dimen.toolbar_button_width);
+        final FrameLayout.LayoutParams menuButtonLayoutParams =
+                (FrameLayout.LayoutParams) mMenuButton.getLayoutParams();
+        menuButtonLayoutParams.width = buttonWidth;
+        menuButtonLayoutParams.gravity = Gravity.CENTER_VERTICAL | Gravity.START;
+        mMenuButton.setLayoutParams(menuButtonLayoutParams);
+        mMenuButton.setPaddingRelative(0, 0, 0, 0);
+
+        ((FrameLayout.LayoutParams) mCloseButton.getLayoutParams()).gravity =
+                Gravity.CENTER_VERTICAL | Gravity.END;
+
+        ((FrameLayout.LayoutParams) mCustomActionButtons.getLayoutParams())
+                .setMarginEnd(buttonWidth);
+    }
+
     private void updateToolbarLayoutMargin() {
         final boolean shouldShowIncognitoIcon = getToolbarDataProvider().isIncognito();
         mIncognitoImageView.setVisibility(shouldShowIncognitoIcon ? VISIBLE : GONE);
 
-        int startMargin = calculateStartMarginWhenCloseButtonVisibilityGone();
+        int startMargin = calculateStartMarginForStartButtonVisibility();
 
         updateStartMarginOfVisibleElementsUntilLocationBarFrameLayout(startMargin);
 
@@ -341,10 +392,12 @@
         mLocationBar.updateLeftMarginOfTitleUrlContainer();
     }
 
-    private int calculateStartMarginWhenCloseButtonVisibilityGone() {
-        return (mCloseButton.getVisibility() == GONE) ? getResources().getDimensionPixelSize(
-                       R.dimen.custom_tabs_toolbar_horizontal_margin_no_close)
-                                                      : 0;
+    private int calculateStartMarginForStartButtonVisibility() {
+        final View buttonAtStart =
+                mCloseButtonPosition == CLOSE_BUTTON_POSITION_END ? mMenuButton : mCloseButton;
+        return (buttonAtStart.getVisibility() == GONE) ? getResources().getDimensionPixelSize(
+                       R.dimen.custom_tabs_toolbar_horizontal_margin_no_start)
+                                                       : 0;
     }
 
     private void updateStartMarginOfVisibleElementsUntilLocationBarFrameLayout(int startMargin) {
@@ -497,7 +550,9 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        maybeSwapCloseAndMenuButtons();
         updateToolbarLayoutMargin();
+        maybeAdjustButtonSpacingForCloseButtonPosition();
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityRenderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityRenderTest.java
index 34c4abe..68fe8a8 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityRenderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityRenderTest.java
@@ -4,6 +4,8 @@
 
 package org.chromium.chrome.browser.customtabs;
 
+import static org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.CLOSE_BUTTON_POSITION_END;
+import static org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider.EXTRA_CLOSE_BUTTON_POSITION;
 import static org.chromium.chrome.browser.customtabs.CustomTabsTestUtils.createTestBitmap;
 
 import android.app.PendingIntent;
@@ -176,6 +178,15 @@
     @Test
     @MediumTest
     @Feature("RenderTest")
+    public void testCCTToolbarWithEndCloseButton() throws IOException {
+        mIntent.putExtra(EXTRA_CLOSE_BUTTON_POSITION, CLOSE_BUTTON_POSITION_END);
+
+        startActivityAndRenderToolbar("cct_close_button_end_with_https_" + mRunWithHttps);
+    }
+
+    @Test
+    @MediumTest
+    @Feature("RenderTest")
     public void custom_color_red() throws IOException {
         Context context = InstrumentationRegistry.getContext();
         mIntent = CustomTabsTestUtils.createCustomTabIntent(
diff --git a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/BrowserServicesIntentDataProvider.java b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/BrowserServicesIntentDataProvider.java
index a69881f..bdf36b5 100644
--- a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/BrowserServicesIntentDataProvider.java
+++ b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/BrowserServicesIntentDataProvider.java
@@ -57,6 +57,25 @@
         int V2_NOTIFICATION_OR_SNACKBAR = 1;
     }
 
+    @IntDef({CLOSE_BUTTON_POSITION_DEFAULT, CLOSE_BUTTON_POSITION_START, CLOSE_BUTTON_POSITION_END})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CloseButtonPosition {}
+
+    /** Same as {@link #CLOSE_BUTTON_POSITION_START}. */
+    public static final int CLOSE_BUTTON_POSITION_DEFAULT = 0;
+
+    /** Positions the close button at the start of the toolbar. */
+    public static final int CLOSE_BUTTON_POSITION_START = 1;
+
+    /** Positions the close button at the end of the toolbar. */
+    public static final int CLOSE_BUTTON_POSITION_END = 2;
+
+    /**
+     * Maximum value for the CLOSE_BUTTON_POSITION_* configuration options. For validation purposes
+     * only.
+     */
+    public static final int CLOSE_BUTTON_POSITION_MAX = 2;
+
     /**
      * @return The type of the Activity;
      */
@@ -456,4 +475,11 @@
     public @Px int getInitialActivityHeight() {
         return 0;
     }
+
+    /**
+     * Returns the {@link CloseButtonPosition}.
+     */
+    public @CloseButtonPosition int getCloseButtonPosition() {
+        return CLOSE_BUTTON_POSITION_DEFAULT;
+    }
 }