diff --git a/android/java/org/chromium/chrome/browser/app/BraveActivity.java b/android/java/org/chromium/chrome/browser/app/BraveActivity.java index 42b3310370b..49c2aa6e54b 100644 --- a/android/java/org/chromium/chrome/browser/app/BraveActivity.java +++ b/android/java/org/chromium/chrome/browser/app/BraveActivity.java @@ -145,6 +145,8 @@ import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.browser.fullscreen.BrowserControlsManager; import org.chromium.chrome.browser.fullscreen.FullscreenManager; +import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthController; +import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthManager; import org.chromium.chrome.browser.informers.BraveSyncAccountDeletedInformer; import org.chromium.chrome.browser.lifetime.ApplicationLifetime; import org.chromium.chrome.browser.misc_metrics.MiscAndroidMetricsConnectionErrorHandler; @@ -357,6 +359,7 @@ public abstract class BraveActivity extends ChromeActivity private SearchWidgetPromoPanel mSearchWidgetPromoPanel; private ApplicationStateListener mApplicationStateListener; + private IncognitoReauthController mIncognitoReauthController; /** Serves as a general exception for failed attempts to get BraveActivity. */ public static class BraveActivityNotFoundException extends Exception { @@ -2461,6 +2464,9 @@ public abstract class BraveActivity extends ChromeActivity dbUtil.cleanUpDbOperationRequest(); } super.performPreInflationStartup(); + mRootUiCoordinator + .getIncognitoReauthControllerSupplier() + .onAvailable(irc -> mIncognitoReauthController = irc); } @Override @@ -2824,6 +2830,30 @@ public abstract class BraveActivity extends ChromeActivity } } + @Override + public boolean isShredButtonVisible() { + return mIncognitoReauthController == null + || !mIncognitoReauthController.isIncognitoReauthPending(); + } + + @Override + public void setShredButtonVisibilityObserver( + IncognitoReauthManager.IncognitoReauthCallback callback) { + if (mIncognitoReauthController == null) { + return; + } + mIncognitoReauthController.addIncognitoReauthCallback(callback); + } + + @Override + public void removeShredButtonVisibilityObserver( + IncognitoReauthManager.IncognitoReauthCallback callback) { + if (mIncognitoReauthController == null) { + return; + } + mIncognitoReauthController.removeIncognitoReauthCallback(callback); + } + private void shredData(Tab currentTab) { LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); diff --git a/android/junit/BUILD.gn b/android/junit/BUILD.gn index f9bc31ddab5..6e805ad9b7d 100644 --- a/android/junit/BUILD.gn +++ b/android/junit/BUILD.gn @@ -218,4 +218,17 @@ if (is_android) { deps = [ "//chrome/android/junit:chrome_junit_tests_helper" ] } + + robolectric_library("brave_junit_tests_org.chromium.chrome.browser.hub") { + sources = [ + "src/org/chromium/chrome/browser/hub/BraveHubToolbarViewUnitTest.java", + ] + + deps = [ + "//brave/browser/brave_shields/android:java", + "//chrome/android:chrome_java", + "//chrome/android/junit:chrome_junit_tests_helper", + "//chrome/browser/hub/internal:java", + ] + } } diff --git a/android/junit/src/org/chromium/chrome/browser/hub/BraveHubToolbarViewUnitTest.java b/android/junit/src/org/chromium/chrome/browser/hub/BraveHubToolbarViewUnitTest.java new file mode 100644 index 00000000000..3781ca79340 --- /dev/null +++ b/android/junit/src/org/chromium/chrome/browser/hub/BraveHubToolbarViewUnitTest.java @@ -0,0 +1,136 @@ +/* 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.hub; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import static org.chromium.chrome.browser.hub.HubColorMixer.COLOR_MIXER; +import static org.chromium.chrome.browser.hub.HubToolbarProperties.IS_INCOGNITO; + +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.FrameLayout; + +import androidx.test.ext.junit.rules.ActivityScenarioRule; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.annotation.Config; + +import org.chromium.base.BraveFeatureList; +import org.chromium.base.supplier.ObservableSuppliers; +import org.chromium.base.supplier.SettableMonotonicObservableSupplier; +import org.chromium.base.test.BaseRobolectricTestRunner; +import org.chromium.base.test.util.CommandLineFlags; +import org.chromium.base.test.util.Features.DisableFeatures; +import org.chromium.base.test.util.Features.EnableFeatures; +import org.chromium.chrome.R; +import org.chromium.chrome.browser.ChromeTabbedActivity; +import org.chromium.chrome.browser.app.BraveActivity; +import org.chromium.chrome.browser.brave_shields.FirstPartyStorageCleanerInterface; +import org.chromium.chrome.browser.flags.ChromeSwitches; +import org.chromium.ui.modelutil.PropertyModel; +import org.chromium.ui.modelutil.PropertyModelChangeProcessor; + +/** Unit tests for {@link BraveHubToolbarView}. */ +@RunWith(BaseRobolectricTestRunner.class) +@Config(manifest = Config.NONE) +@CommandLineFlags.Add({ + ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, + ChromeSwitches.DISABLE_NATIVE_INITIALIZATION +}) +public class BraveHubToolbarViewUnitTest { + @Rule + public ActivityScenarioRule mActivityScenarioRule = + new ActivityScenarioRule<>(ChromeTabbedActivity.class); + + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + private Pane mPane; + private SettableMonotonicObservableSupplier mFocusedPaneSupplier; + private FrameLayout mToolbarContainer; + private Button mShredButton; + private PropertyModel mPropertyModel; + private BraveActivity mActivity; + private HubColorMixer mColorMixer; + private BraveHubToolbarView mHubToolbarView; + + @Before + public void setUp() throws Exception { + mActivityScenarioRule.getScenario().onActivity(this::onActivity); + } + + private void onActivity(ChromeTabbedActivity activity) { + mPane = mock(); + BraveActivity braveActivity = (BraveActivity) (Object) activity; + mActivity = spy(braveActivity); + mActivity.setTheme(R.style.Theme_BrowserUI_DayNight); + + LayoutInflater inflater = LayoutInflater.from(mActivity); + mToolbarContainer = + (FrameLayout) inflater.inflate(R.layout.hub_toolbar_layout, null, false); + mHubToolbarView = (BraveHubToolbarView) mToolbarContainer.findViewById(R.id.hub_toolbar); + mShredButton = + mToolbarContainer.findViewById( + org.chromium.chrome.browser.brave_shields.R.id.shred_data_button); + mActivity.setContentView(mToolbarContainer); + + mHubToolbarView.setFirstPartyStorageCleanerForTesting( + (FirstPartyStorageCleanerInterface) mActivity); + + mFocusedPaneSupplier = ObservableSuppliers.createMonotonic(); + mColorMixer = + spy( + new HubColorMixerImpl( + mActivity, ObservableSuppliers.alwaysTrue(), mFocusedPaneSupplier)); + mPropertyModel = + new PropertyModel.Builder(HubToolbarProperties.ALL_KEYS) + .with(COLOR_MIXER, mColorMixer) + .build(); + PropertyModelChangeProcessor.create( + mPropertyModel, mHubToolbarView, HubToolbarViewBinder::bind); + when(mPane.getColorScheme()).thenReturn(HubColorScheme.DEFAULT); + mFocusedPaneSupplier.set(mPane); + } + + @Test + @EnableFeatures({BraveFeatureList.BRAVE_SHRED}) + public void testHideManualShredButtonInIncognitoMode() { + when(mActivity.isShredButtonVisible()).thenReturn(true); + assertEquals(View.VISIBLE, mShredButton.getVisibility()); + + when(mActivity.isShredButtonVisible()).thenReturn(false); + mPropertyModel.set(IS_INCOGNITO, true); + assertEquals(View.INVISIBLE, mShredButton.getVisibility()); + + when(mActivity.isShredButtonVisible()).thenReturn(true); + mPropertyModel.set(IS_INCOGNITO, false); + assertEquals(View.VISIBLE, mShredButton.getVisibility()); + } + + @Test + @DisableFeatures({BraveFeatureList.BRAVE_SHRED}) + public void testHideManualShredButtonIfFeatureFlagDisabled() { + when(mActivity.isShredButtonVisible()).thenReturn(true); + assertEquals(View.VISIBLE, mShredButton.getVisibility()); + + when(mActivity.isShredButtonVisible()).thenReturn(false); + mPropertyModel.set(IS_INCOGNITO, true); + assertEquals(View.INVISIBLE, mShredButton.getVisibility()); + + when(mActivity.isShredButtonVisible()).thenReturn(true); + mPropertyModel.set(IS_INCOGNITO, false); + assertEquals(View.INVISIBLE, mShredButton.getVisibility()); + } +} diff --git a/browser/brave_shields/android/BUILD.gn b/browser/brave_shields/android/BUILD.gn index 855edacfd31..6a55c662054 100644 --- a/browser/brave_shields/android/BUILD.gn +++ b/browser/brave_shields/android/BUILD.gn @@ -19,6 +19,7 @@ android_library("java") { deps = [ ":java_resources", "//base:base_java", + "//chrome/browser/incognito:java", "//chrome/browser/profiles/android:java", "//chrome/browser/tab:java", "//third_party/android_deps:com_airbnb_android_lottie_java", diff --git a/browser/brave_shields/android/java/src/org/chromium/chrome/browser/brave_shields/FirstPartyStorageCleanerInterface.java b/browser/brave_shields/android/java/src/org/chromium/chrome/browser/brave_shields/FirstPartyStorageCleanerInterface.java index afeb6daaf19..c929b523a19 100644 --- a/browser/brave_shields/android/java/src/org/chromium/chrome/browser/brave_shields/FirstPartyStorageCleanerInterface.java +++ b/browser/brave_shields/android/java/src/org/chromium/chrome/browser/brave_shields/FirstPartyStorageCleanerInterface.java @@ -6,8 +6,16 @@ package org.chromium.chrome.browser.brave_shields; import org.chromium.build.annotations.NullMarked; +import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthManager; @NullMarked public interface FirstPartyStorageCleanerInterface { void shredSiteData(); + + boolean isShredButtonVisible(); + + void setShredButtonVisibilityObserver(IncognitoReauthManager.IncognitoReauthCallback callback); + + void removeShredButtonVisibilityObserver( + IncognitoReauthManager.IncognitoReauthCallback callback); } diff --git a/browser/hub/internal/android/java/java_sources.gni b/browser/hub/internal/android/java/java_sources.gni index ee8e8722c8d..98b66fdc3d9 100644 --- a/browser/hub/internal/android/java/java_sources.gni +++ b/browser/hub/internal/android/java/java_sources.gni @@ -13,5 +13,6 @@ brave_browser_hub_internal_java_sources = [ brave_browser_hub_internal_java_deps = [ "//base:base_shared_preferences_java", "//brave/browser/brave_shields/android:java", + "//chrome/browser/incognito:java", "//chrome/browser/preferences:java", ] diff --git a/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/BraveHubToolbarView.java b/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/BraveHubToolbarView.java index 9efe988eccd..c3538ac1c45 100644 --- a/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/BraveHubToolbarView.java +++ b/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/BraveHubToolbarView.java @@ -14,10 +14,15 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.FrameLayout; +import androidx.annotation.VisibleForTesting; + +import org.chromium.base.ApplicationStatus; import org.chromium.base.BraveFeatureList; import org.chromium.base.BravePreferenceKeys; +import org.chromium.build.annotations.Nullable; import org.chromium.chrome.browser.brave_shields.FirstPartyStorageCleanerInterface; import org.chromium.chrome.browser.flags.ChromeFeatureList; +import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthManager; import org.chromium.chrome.browser.preferences.ChromeSharedPreferences; import org.chromium.chrome.browser.toolbar.settings.AddressBarPreference; @@ -25,10 +30,14 @@ import org.chromium.chrome.browser.toolbar.settings.AddressBarPreference; * Brave's extension for {@link HubToolbarView}. Here we control what elements should be visible in * tab switcher mode when bottom toolbar is visible. */ -public class BraveHubToolbarView extends HubToolbarView { +public class BraveHubToolbarView extends HubToolbarView + implements ApplicationStatus.TaskVisibilityListener, + IncognitoReauthManager.IncognitoReauthCallback { private Button mActionButton; private Button mShredButton; private FrameLayout mMenuButton; + private boolean mIsIncognitoSelected = true; + private @Nullable FirstPartyStorageCleanerInterface mFpCleaner; public BraveHubToolbarView(Context context, AttributeSet attributeSet) { super(context, attributeSet); @@ -37,7 +46,6 @@ public class BraveHubToolbarView extends HubToolbarView { @Override protected void onFinishInflate() { super.onFinishInflate(); - mActionButton = findViewById(R.id.toolbar_action_button); mShredButton = findViewById(org.chromium.chrome.browser.brave_shields.R.id.shred_data_button); @@ -45,15 +53,34 @@ public class BraveHubToolbarView extends HubToolbarView { mShredButton.setOnClickListener( v -> { - Context context = getContext(); - if (context instanceof FirstPartyStorageCleanerInterface) { - FirstPartyStorageCleanerInterface fpCleaner = - (FirstPartyStorageCleanerInterface) context; - fpCleaner.shredSiteData(); + if (mFpCleaner != null) { + mFpCleaner.shredSiteData(); } }); } + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + Context context = getContext(); + if (context instanceof FirstPartyStorageCleanerInterface) { + mFpCleaner = (FirstPartyStorageCleanerInterface) context; + mFpCleaner.setShredButtonVisibilityObserver(this); + } + + ApplicationStatus.registerTaskVisibilityListener(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + ApplicationStatus.unregisterTaskVisibilityListener(this); + if (mFpCleaner != null) { + mFpCleaner.removeShredButtonVisibilityObserver(this); + } + } + @Override void setPaneSwitcherIndex(int index) { super.setPaneSwitcherIndex(index); @@ -62,6 +89,13 @@ public class BraveHubToolbarView extends HubToolbarView { updateButtonsVisibility(); } + @Override + void updateIncognitoElements(boolean isIncognito) { + super.updateIncognitoElements(isIncognito); + mIsIncognitoSelected = isIncognito; + updateButtonsVisibility(); + } + @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -76,10 +110,31 @@ public class BraveHubToolbarView extends HubToolbarView { @Override void setMenuButtonVisible(boolean visible) { super.setMenuButtonVisible(visible); - updateButtonsVisibility(); } + @Override + public void onIncognitoReauthSuccess() { + updateButtonsVisibility(); + } + + @Override + public void onIncognitoReauthNotPossible() {} + + @Override + public void onIncognitoReauthFailure() {} + + @Override + public void onTaskVisibilityChanged(int taskId, boolean isVisible) { + updateButtonsVisibility(); + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + public void setFirstPartyStorageCleanerForTesting( + FirstPartyStorageCleanerInterface firstPartyStorageCleaner) { + mFpCleaner = firstPartyStorageCleaner; + } + private void updateButtonsVisibility() { boolean shouldHideButtons = AddressBarPreference.isToolbarConfiguredToShowOnTop() @@ -104,8 +159,10 @@ public class BraveHubToolbarView extends HubToolbarView { } } + final boolean isShredButtonVisible = + !mIsIncognitoSelected || mFpCleaner == null || mFpCleaner.isShredButtonVisible(); final boolean shouldShowShredButton = - ChromeFeatureList.isEnabled(BraveFeatureList.BRAVE_SHRED); + ChromeFeatureList.isEnabled(BraveFeatureList.BRAVE_SHRED) && isShredButtonVisible; mShredButton.setVisibility(shouldShowShredButton ? View.VISIBLE : View.INVISIBLE); } } diff --git a/test/BUILD.gn b/test/BUILD.gn index b56d6f5ec2a..b393c070f4a 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -1690,6 +1690,7 @@ if (is_android) { "//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.hub", "//brave/android/junit:brave_junit_tests_org.chromium.chrome.browser.ntp", "//brave/android/junit:brave_junit_tests_org.chromium.chrome.browser.preferences.website", "//brave/android/junit:brave_junit_tests_org.chromium.chrome.browser.quick_search_engines",