[Android] Fix for Open in new tab animation (#36974)
Resolves https://github.com/brave/brave-browser/issues/56076 must be reverted for `cr150`
This commit is contained in:
@@ -100,6 +100,7 @@ brave_java_sources = [
|
||||
"../../brave/android/java/org/chromium/chrome/browser/browsing_data/BraveClearBrowsingDataFragment.java",
|
||||
"../../brave/android/java/org/chromium/chrome/browser/browsing_data/BraveShortcutsUtils.java",
|
||||
"../../brave/android/java/org/chromium/chrome/browser/compositor/layouts/BraveToolbarSwipeLayout.java",
|
||||
"../../brave/android/java/org/chromium/chrome/browser/compositor/layouts/phone/BraveNewTabAnimationLayout.java",
|
||||
"../../brave/android/java/org/chromium/chrome/browser/contextmenu/BraveChromeContextMenuPopulator.java",
|
||||
"../../brave/android/java/org/chromium/chrome/browser/cosmetic_filters/BraveCosmeticFiltersUtils.java",
|
||||
"../../brave/android/java/org/chromium/chrome/browser/crash/BravePureJavaExceptionReporter.java",
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.chromium.chrome.browser.compositor.layouts.phone;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.chromium.build.annotations.NullMarked;
|
||||
import org.chromium.chrome.R;
|
||||
import org.chromium.chrome.browser.toolbar.bottom.BottomToolbarConfiguration;
|
||||
|
||||
/**
|
||||
* Brave helper for {@link NewTabAnimationLayout}.
|
||||
*
|
||||
* <p>When Brave's bottom toolbar is active the top tab-switcher button may be GONE (empty rect) or
|
||||
* visible alongside the bottom one. In either case the bottom toolbar's tab-switcher button is the
|
||||
* correct animation destination. {@link #compute} encapsulates the fallback logic and returns a
|
||||
* {@link BraveTabSwitcherState} that callers unpack into the four variables that drive {@code
|
||||
* tabCreatedInBackground}: {@code tabSwitcherRect}, {@code tabSwitcherButtonIsVisible}, {@code
|
||||
* toolbarHeight}, and {@code isTopToolbar}.
|
||||
*
|
||||
* <p>Both classes share the same Java package so no import is needed in the patch.
|
||||
*/
|
||||
@NullMarked
|
||||
public class BraveNewTabAnimationLayout {
|
||||
|
||||
/** Immutable snapshot of the four variables that drive the background-tab animation. */
|
||||
public static class BraveTabSwitcherState {
|
||||
public final Rect rect;
|
||||
public final boolean isVisible;
|
||||
public final int toolbarHeight;
|
||||
public final boolean isTopToolbar;
|
||||
|
||||
BraveTabSwitcherState(
|
||||
Rect rect, boolean isVisible, int toolbarHeight, boolean isTopToolbar) {
|
||||
this.rect = rect;
|
||||
this.isVisible = isVisible;
|
||||
this.toolbarHeight = toolbarHeight;
|
||||
this.isTopToolbar = isTopToolbar;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link BraveTabSwitcherState} for the background-tab animation.
|
||||
*
|
||||
* <p>When Brave's bottom toolbar is active and its tab-switcher button is visible, overrides
|
||||
* the supplied originals so the animation targets the bottom button. Otherwise returns the
|
||||
* originals unchanged (e.g. split-screen small-window mode where the bottom toolbar is hidden,
|
||||
* or when Brave bottom controls are not enabled).
|
||||
*
|
||||
* @param animationHostView The host view used to traverse to the root view.
|
||||
* @param origRect Original {@code tabSwitcherRect} from the top toolbar button.
|
||||
* @param origIsVisible Original {@code tabSwitcherButtonIsVisible}.
|
||||
* @param origToolbarHeight Original {@code toolbarHeight} (top-toolbar Y).
|
||||
* @param origIsTopToolbar Original {@code isTopToolbar} flag.
|
||||
*/
|
||||
public static BraveTabSwitcherState compute(
|
||||
ViewGroup animationHostView,
|
||||
Rect origRect,
|
||||
boolean origIsVisible,
|
||||
int origToolbarHeight,
|
||||
boolean origIsTopToolbar) {
|
||||
if (BottomToolbarConfiguration.isBraveBottomControlsEnabled()) {
|
||||
View bottomToolbar = animationHostView.getRootView().findViewById(R.id.bottom_toolbar);
|
||||
if (bottomToolbar != null) {
|
||||
View bottomButton = bottomToolbar.findViewById(R.id.bottom_tab_switcher_button);
|
||||
if (bottomButton != null) {
|
||||
Rect braveRect = new Rect();
|
||||
if (bottomButton.getGlobalVisibleRect(braveRect)) {
|
||||
return new BraveTabSwitcherState(
|
||||
braveRect,
|
||||
/* isVisible= */ true,
|
||||
/* toolbarHeight= */ braveRect.top,
|
||||
/* isTopToolbar= */ false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new BraveTabSwitcherState(
|
||||
origRect, origIsVisible, origToolbarHeight, origIsTopToolbar);
|
||||
}
|
||||
|
||||
private BraveNewTabAnimationLayout() {}
|
||||
}
|
||||
@@ -10,6 +10,14 @@ import("//third_party/jni_zero/jni_zero.gni")
|
||||
testonly = true
|
||||
|
||||
if (is_android) {
|
||||
robolectric_library(
|
||||
"brave_junit_tests_org.chromium.chrome.browser.compositor") {
|
||||
resources_package = "org.chromium.chrome"
|
||||
sources = [ "src/org/chromium/chrome/browser/compositor/layouts/phone/BraveNewTabAnimationLayoutUnitTest.java" ]
|
||||
|
||||
deps = [ "//chrome/android/junit:chrome_junit_tests_helper" ]
|
||||
}
|
||||
|
||||
robolectric_library(
|
||||
"brave_junit_tests_org.chromium.chrome.browser.homepage") {
|
||||
sources = [
|
||||
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.chromium.chrome.browser.compositor.layouts.phone;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import org.chromium.base.BravePreferenceKeys;
|
||||
import org.chromium.base.test.BaseRobolectricTestRunner;
|
||||
import org.chromium.chrome.R;
|
||||
import org.chromium.chrome.browser.compositor.layouts.phone.BraveNewTabAnimationLayout.BraveTabSwitcherState;
|
||||
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
|
||||
|
||||
/** Unit tests for {@link BraveNewTabAnimationLayout}. */
|
||||
@RunWith(BaseRobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class BraveNewTabAnimationLayoutUnitTest {
|
||||
private static final int ORIG_TOOLBAR_HEIGHT = 150;
|
||||
private static final int BOTTOM_LEFT = 700;
|
||||
private static final int BOTTOM_TOP = 1900;
|
||||
private static final int BOTTOM_RIGHT = 850;
|
||||
private static final int BOTTOM_BOTTOM = 2000;
|
||||
|
||||
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
|
||||
@Mock private ViewGroup mAnimationHostView;
|
||||
@Mock private View mRootView;
|
||||
@Mock private View mBottomToolbar;
|
||||
@Mock private View mBottomTabSwitcherButton;
|
||||
|
||||
private final Rect mOrigRect = new Rect(0, 0, 0, 0);
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
// Wire getRootView() -> mRootView -> bottom_toolbar -> bottom_tab_switcher_button.
|
||||
lenient().when(mAnimationHostView.getRootView()).thenReturn(mRootView);
|
||||
lenient().when(mRootView.findViewById(R.id.bottom_toolbar)).thenReturn(mBottomToolbar);
|
||||
lenient()
|
||||
.when(mBottomToolbar.findViewById(R.id.bottom_tab_switcher_button))
|
||||
.thenReturn(mBottomTabSwitcherButton);
|
||||
lenient()
|
||||
.when(mBottomTabSwitcherButton.getGlobalVisibleRect(any(Rect.class)))
|
||||
.thenAnswer(
|
||||
invocation -> {
|
||||
Rect r = invocation.getArgument(0);
|
||||
r.set(BOTTOM_LEFT, BOTTOM_TOP, BOTTOM_RIGHT, BOTTOM_BOTTOM);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
ChromeSharedPreferences.getInstance()
|
||||
.writeBoolean(BravePreferenceKeys.BRAVE_BOTTOM_TOOLBAR_SET_KEY, false);
|
||||
}
|
||||
|
||||
private void enableBraveBottomControls() {
|
||||
ChromeSharedPreferences.getInstance()
|
||||
.writeBoolean(BravePreferenceKeys.BRAVE_BOTTOM_TOOLBAR_SET_KEY, true);
|
||||
ChromeSharedPreferences.getInstance()
|
||||
.writeBoolean(BravePreferenceKeys.BRAVE_BOTTOM_TOOLBAR_ENABLED_KEY, true);
|
||||
}
|
||||
|
||||
private void disableBraveBottomControls() {
|
||||
ChromeSharedPreferences.getInstance()
|
||||
.writeBoolean(BravePreferenceKeys.BRAVE_BOTTOM_TOOLBAR_SET_KEY, true);
|
||||
ChromeSharedPreferences.getInstance()
|
||||
.writeBoolean(BravePreferenceKeys.BRAVE_BOTTOM_TOOLBAR_ENABLED_KEY, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
public void testCompute_bottomEnabled_bottomButtonVisible_returnsBottomState() {
|
||||
enableBraveBottomControls();
|
||||
|
||||
BraveTabSwitcherState s =
|
||||
BraveNewTabAnimationLayout.compute(
|
||||
mAnimationHostView,
|
||||
mOrigRect,
|
||||
/* origIsVisible= */ false,
|
||||
ORIG_TOOLBAR_HEIGHT,
|
||||
/* origIsTopToolbar= */ true);
|
||||
|
||||
assertFalse("Rect should not be empty", s.rect.isEmpty());
|
||||
assertEquals(BOTTOM_LEFT, s.rect.left);
|
||||
assertEquals(BOTTOM_TOP, s.rect.top);
|
||||
assertEquals(BOTTOM_RIGHT, s.rect.right);
|
||||
assertEquals(BOTTOM_BOTTOM, s.rect.bottom);
|
||||
assertTrue("isVisible should be true", s.isVisible);
|
||||
assertEquals("toolbarHeight should equal bottom button top", BOTTOM_TOP, s.toolbarHeight);
|
||||
assertFalse("isTopToolbar should be false", s.isTopToolbar);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
public void testCompute_bottomDisabled_returnsOriginals() {
|
||||
// Bottom address bar is enabled
|
||||
disableBraveBottomControls();
|
||||
|
||||
BraveTabSwitcherState s =
|
||||
BraveNewTabAnimationLayout.compute(
|
||||
mAnimationHostView,
|
||||
mOrigRect,
|
||||
/* origIsVisible= */ false,
|
||||
ORIG_TOOLBAR_HEIGHT,
|
||||
/* origIsTopToolbar= */ true);
|
||||
|
||||
assertTrue("Rect should be the original empty rect", s.rect.isEmpty());
|
||||
assertFalse("isVisible should be the original false", s.isVisible);
|
||||
assertEquals(ORIG_TOOLBAR_HEIGHT, s.toolbarHeight);
|
||||
assertTrue("isTopToolbar should be the original true", s.isTopToolbar);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
public void testCompute_bottomEnabled_bottomButtonNotVisible_returnsOriginals() {
|
||||
// Split-screen / small-window case
|
||||
enableBraveBottomControls();
|
||||
when(mBottomTabSwitcherButton.getGlobalVisibleRect(any(Rect.class))).thenReturn(false);
|
||||
|
||||
BraveTabSwitcherState s =
|
||||
BraveNewTabAnimationLayout.compute(
|
||||
mAnimationHostView,
|
||||
mOrigRect,
|
||||
/* origIsVisible= */ false,
|
||||
ORIG_TOOLBAR_HEIGHT,
|
||||
/* origIsTopToolbar= */ true);
|
||||
|
||||
assertTrue("Rect should be the original when bottom button not visible", s.rect.isEmpty());
|
||||
assertEquals(ORIG_TOOLBAR_HEIGHT, s.toolbarHeight);
|
||||
assertTrue(
|
||||
"isTopToolbar should be original when bottom button not visible", s.isTopToolbar);
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewTabAnimationLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewTabAnimationLayout.java
|
||||
index 9433bb6de83f05fe8c9b269a43119418a325578b..37f981bd9f86da89cbef7c89b285bbdf307e6f20 100644
|
||||
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewTabAnimationLayout.java
|
||||
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewTabAnimationLayout.java
|
||||
@@ -700,6 +700,7 @@ public class NewTabAnimationLayout extends Layout {
|
||||
isRegularNtp || ToolbarPositionController.shouldShowToolbarOnTop(animationTab);
|
||||
int toolbarHeight = toolbarPosition[1] + getTopInsetIfNeeded(animationTab);
|
||||
|
||||
+ BraveNewTabAnimationLayout.BraveTabSwitcherState braveState = BraveNewTabAnimationLayout.compute(mAnimationHostView, tabSwitcherRect, tabSwitcherButtonIsVisible, toolbarHeight, isTopToolbar); tabSwitcherRect = braveState.rect; tabSwitcherButtonIsVisible = braveState.isVisible; toolbarHeight = braveState.toolbarHeight; isTopToolbar = braveState.isTopToolbar;
|
||||
Rect compositorViewRect = new Rect();
|
||||
mCompositorViewHolder.getGlobalVisibleRect(compositorViewRect);
|
||||
|
||||
@@ -1687,6 +1687,7 @@ if (is_android) {
|
||||
"//brave/android/junit:brave_junit_tests_org.chromium.chrome.browser.app",
|
||||
"//brave/android/junit:brave_junit_tests_org.chromium.chrome.browser.autofill",
|
||||
"//brave/android/junit:brave_junit_tests_org.chromium.chrome.browser.brave_origin",
|
||||
"//brave/android/junit:brave_junit_tests_org.chromium.chrome.browser.compositor",
|
||||
"//brave/android/junit:brave_junit_tests_org.chromium.chrome.browser.custom_search_engines",
|
||||
"//brave/android/junit:brave_junit_tests_org.chromium.chrome.browser.homepage",
|
||||
"//brave/android/junit:brave_junit_tests_org.chromium.chrome.browser.ntp",
|
||||
|
||||
Reference in New Issue
Block a user