diff --git a/android/brave_java_sources.gni b/android/brave_java_sources.gni index 316597eb83c..c987eaa5d5e 100644 --- a/android/brave_java_sources.gni +++ b/android/brave_java_sources.gni @@ -420,6 +420,7 @@ brave_java_sources = [ "../../brave/android/java/org/chromium/chrome/browser/tasks/BraveReturnToChromeUtil.java", "../../brave/android/java/org/chromium/chrome/browser/toolbar/BraveHomeButton.java", "../../brave/android/java/org/chromium/chrome/browser/toolbar/BraveToolbarManager.java", + "../../brave/android/java/org/chromium/chrome/browser/toolbar/adaptive/BraveBookmarksButtonController.java", "../../brave/android/java/org/chromium/chrome/browser/toolbar/bottom/BookmarksButton.java", "../../brave/android/java/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarConfiguration.java", "../../brave/android/java/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarCoordinator.java", @@ -440,6 +441,7 @@ brave_java_sources = [ "../../brave/android/java/org/chromium/chrome/browser/toolbar/menu_button/BraveMenuButtonCoordinator.java", "../../brave/android/java/org/chromium/chrome/browser/toolbar/top/BraveToolbarLayoutImpl.java", "../../brave/android/java/org/chromium/chrome/browser/toolbar/top/BraveTopToolbarCoordinator.java", + "../../brave/android/java/org/chromium/chrome/browser/ui/BraveAdaptiveToolbarUiCoordinator.java", "../../brave/android/java/org/chromium/chrome/browser/ui/messages/infobar/BraveSimpleConfirmInfoBarBuilder.java", "../../brave/android/java/org/chromium/chrome/browser/ui/system/BraveStatusBarColorController.java", "../../brave/android/java/org/chromium/chrome/browser/undo_tab_close_snackbar/BraveUndoBarControllerBase.java", diff --git a/android/java/apk_for_test.flags b/android/java/apk_for_test.flags index 637e31c5021..416bf4804b2 100644 --- a/android/java/apk_for_test.flags +++ b/android/java/apk_for_test.flags @@ -1030,3 +1030,38 @@ -keep class org.chromium.chrome.browser.download.home.search.BraveSearchBarCoordinator { (...); } + +-keep class org.chromium.chrome.browser.ui.AdaptiveToolbarUiCoordinator { + (...); +} + +-keep class org.chromium.chrome.browser.ui.BraveAdaptiveToolbarUiCoordinator { + (...); +} + +-keep class org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarStatePredictor { + *** isValidSegment(...); +} + +-keep class org.chromium.chrome.browser.toolbar.adaptive.settings.RadioButtonGroupAdaptiveToolbarPreference { + *** mIsBound; + *** mAutoButton; + *** mNewTabButton; + *** mShareButton; +} + +-keep class org.chromium.chrome.browser.ui.AdaptiveToolbarUiCoordinator { + *** mContext; + *** mActivityTabProvider; + *** mModalDialogManagerSupplier; + *** mProfileSupplier; + *** mAdaptiveToolbarButtonController; +} + +-keep class org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarStatePredictor { + public (...); +} + +-keep class org.chromium.chrome.browser.toolbar.adaptive.BraveAdaptiveToolbarStatePredictor { + public (...); +} diff --git a/android/java/org/chromium/chrome/browser/tabbed_mode/BraveTabbedRootUiCoordinator.java b/android/java/org/chromium/chrome/browser/tabbed_mode/BraveTabbedRootUiCoordinator.java index b88a5e297b1..7b8684676c7 100644 --- a/android/java/org/chromium/chrome/browser/tabbed_mode/BraveTabbedRootUiCoordinator.java +++ b/android/java/org/chromium/chrome/browser/tabbed_mode/BraveTabbedRootUiCoordinator.java @@ -22,6 +22,7 @@ import org.chromium.chrome.browser.ActivityTabProvider; import org.chromium.chrome.browser.app.BraveActivity; import org.chromium.chrome.browser.back_press.BackPressManager; import org.chromium.chrome.browser.bookmarks.BookmarkManagerOpener; +import org.chromium.chrome.browser.bookmarks.BookmarkManagerOpenerImpl; import org.chromium.chrome.browser.bookmarks.BookmarkModel; import org.chromium.chrome.browser.bookmarks.TabBookmarker; import org.chromium.chrome.browser.compositor.CompositorViewHolder; @@ -44,6 +45,7 @@ import org.chromium.chrome.browser.tab_ui.TabSwitcher; import org.chromium.chrome.browser.tabmodel.TabCreatorManager; import org.chromium.chrome.browser.tabmodel.TabModelSelector; import org.chromium.chrome.browser.toolbar.ToolbarIntentMetadata; +import org.chromium.chrome.browser.ui.BraveAdaptiveToolbarUiCoordinator; import org.chromium.chrome.browser.ui.appmenu.AppMenuBlocker; import org.chromium.chrome.browser.ui.appmenu.AppMenuDelegate; import org.chromium.chrome.browser.ui.edge_to_edge.EdgeToEdgeController; @@ -206,4 +208,16 @@ public class BraveTabbedRootUiCoordinator extends TabbedRootUiCoordinator { hubManager.setStatusIndicatorHeight(bottomToolbarHeight); }); } + + @Override + protected void initializeToolbar() { + super.initializeToolbar(); + + assert mAdaptiveToolbarUiCoordinator instanceof BraveAdaptiveToolbarUiCoordinator + : "Bytecode change was not applied!"; + if (mAdaptiveToolbarUiCoordinator + instanceof BraveAdaptiveToolbarUiCoordinator braveCoordinator) { + braveCoordinator.initializeBrave(new BookmarkManagerOpenerImpl()); + } + } } diff --git a/android/java/org/chromium/chrome/browser/toolbar/adaptive/BraveBookmarksButtonController.java b/android/java/org/chromium/chrome/browser/toolbar/adaptive/BraveBookmarksButtonController.java new file mode 100644 index 00000000000..9ab168cb20d --- /dev/null +++ b/android/java/org/chromium/chrome/browser/toolbar/adaptive/BraveBookmarksButtonController.java @@ -0,0 +1,74 @@ +/* Copyright (c) 2025 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.toolbar.adaptive; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.view.View; + +import org.chromium.base.supplier.ObservableSupplier; +import org.chromium.build.annotations.NullMarked; +import org.chromium.build.annotations.Nullable; +import org.chromium.chrome.R; +import org.chromium.chrome.browser.ActivityTabProvider; +import org.chromium.chrome.browser.bookmarks.BookmarkManagerOpener; +import org.chromium.chrome.browser.profiles.Profile; +import org.chromium.chrome.browser.tab.Tab; +import org.chromium.chrome.browser.toolbar.optional_button.BaseButtonDataProvider; +import org.chromium.ui.modaldialog.ModalDialogManager; + +/** Handles displaying bookmarks button on toolbar. */ +@NullMarked +public class BraveBookmarksButtonController extends BaseButtonDataProvider { + private final Context mContext; + private final ObservableSupplier mProfileSupplier; + private final BookmarkManagerOpener mBookmarkManagerOpener; + + public BraveBookmarksButtonController( + Context context, + Drawable buttonDrawable, + ActivityTabProvider tabProvider, + ObservableSupplier profileSupplier, + ModalDialogManager modalDialogManager, + BookmarkManagerOpener bookmarkManagerOpener) { + super( + tabProvider, + modalDialogManager, + buttonDrawable, + context.getString(R.string.menu_bookmarks), + /* actionChipLabelResId= */ Resources.ID_NULL, + /* supportsTinting= */ true, + /* iphCommandBuilder= */ null, + AdaptiveToolbarButtonVariant.BOOKMARKS, + /* tooltipTextResId= */ R.string.menu_bookmarks); + + mContext = context; + mProfileSupplier = profileSupplier; + mBookmarkManagerOpener = bookmarkManagerOpener; + } + + @Override + public void onClick(View view) { + Profile profile = mProfileSupplier.get(); + if (profile == null) return; + + assert mContext instanceof Activity : "Context is not an Activity"; + if (!(mContext instanceof Activity)) return; + + mBookmarkManagerOpener.showBookmarkManager( + (Activity) mContext, mActiveTabSupplier.get(), profile, /* folderId= */ null); + } + + @Override + protected boolean shouldShowButton(@Nullable Tab tab) { + if (!super.shouldShowButton(tab)) return false; + + // Show the `Bookmarks` button for all tabs + return true; + } +} diff --git a/android/java/org/chromium/chrome/browser/ui/BraveAdaptiveToolbarUiCoordinator.java b/android/java/org/chromium/chrome/browser/ui/BraveAdaptiveToolbarUiCoordinator.java new file mode 100644 index 00000000000..929e2ce6b1d --- /dev/null +++ b/android/java/org/chromium/chrome/browser/ui/BraveAdaptiveToolbarUiCoordinator.java @@ -0,0 +1,79 @@ +/* Copyright (c) 2025 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.ui; + +import android.content.Context; + +import androidx.annotation.Nullable; +import androidx.appcompat.content.res.AppCompatResources; + +import org.chromium.base.supplier.ObservableSupplier; +import org.chromium.base.supplier.Supplier; +import org.chromium.build.annotations.NullMarked; +import org.chromium.chrome.R; +import org.chromium.chrome.browser.ActivityTabProvider; +import org.chromium.chrome.browser.bookmarks.BookmarkManagerOpener; +import org.chromium.chrome.browser.profiles.Profile; +import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarButtonController; +import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarButtonVariant; +import org.chromium.chrome.browser.toolbar.adaptive.BraveBookmarksButtonController; +import org.chromium.ui.modaldialog.ModalDialogManager; + +/** + * Brave-specific coordinator for adaptive toolbar UI components. Extends the base + * AdaptiveToolbarUiCoordinator with Brave-specific functionality. + */ +@NullMarked +public class BraveAdaptiveToolbarUiCoordinator extends AdaptiveToolbarUiCoordinator { + // Variables below will be removed in bytecode, variables from the parent class will be used + // instead. + @Nullable private Context mContext; + @Nullable private ActivityTabProvider mActivityTabProvider; + @Nullable private Supplier mModalDialogManagerSupplier; + @Nullable private ObservableSupplier mProfileSupplier; + @Nullable private AdaptiveToolbarButtonController mAdaptiveToolbarButtonController; + + public BraveAdaptiveToolbarUiCoordinator( + Context context, + ActivityTabProvider activityTabProvider, + Supplier modalDialogManagerSupplier) { + super(context, activityTabProvider, modalDialogManagerSupplier); + } + + /** + * Initialize Brave-specific adaptive toolbar components. + * + * @param bookmarkManagerOpener The bookmark manager opener for the `Bookmarks` button. + */ + public void initializeBrave(BookmarkManagerOpener bookmarkManagerOpener) { + assert mContext != null + && mActivityTabProvider != null + && mModalDialogManagerSupplier != null + && mProfileSupplier != null + : "Bytecode changes were not applied!"; + if (mContext == null + || mActivityTabProvider == null + || mModalDialogManagerSupplier == null + || mProfileSupplier == null) { + return; + } + assert mAdaptiveToolbarButtonController != null + : "initializeBrave must be called after initialize!"; + if (mAdaptiveToolbarButtonController == null) { + return; + } + var bookmarksButtonController = + new BraveBookmarksButtonController( + mContext, + AppCompatResources.getDrawable(mContext, R.drawable.brave_menu_bookmarks), + mActivityTabProvider, + mProfileSupplier, + mModalDialogManagerSupplier.get(), + bookmarkManagerOpener); + mAdaptiveToolbarButtonController.addButtonVariant( + AdaptiveToolbarButtonVariant.BOOKMARKS, bookmarksButtonController); + } +} diff --git a/android/javatests/org/chromium/chrome/browser/BytecodeTest.java b/android/javatests/org/chromium/chrome/browser/BytecodeTest.java index 4fd9397843d..147882bba81 100644 --- a/android/javatests/org/chromium/chrome/browser/BytecodeTest.java +++ b/android/javatests/org/chromium/chrome/browser/BytecodeTest.java @@ -1017,6 +1017,13 @@ public class BytecodeTest { View.class, Context.class, boolean.class)); + Assert.assertTrue( + methodExists( + "org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarStatePredictor", // presubmit: ignore-long-line + "isValidSegment", + MethodModifier.REGULAR, + boolean.class, + int.class)); } @Test @@ -2011,6 +2018,13 @@ public class BytecodeTest { Context.class, Callback.class, boolean.class)); + Assert.assertTrue( + constructorsMatch( + "org/chromium/chrome/browser/ui/AdaptiveToolbarUiCoordinator", + "org/chromium/chrome/browser/ui/BraveAdaptiveToolbarUiCoordinator", + Context.class, + ActivityTabProvider.class, + Supplier.class)); } @Test @@ -2440,6 +2454,41 @@ public class BytecodeTest { "sProfileMap", true, ProfileKeyedMap.class)); + Assert.assertTrue( + fieldExists( + "org/chromium/chrome/browser/toolbar/adaptive/settings/RadioButtonGroupAdaptiveToolbarPreference", // presubmit: ignore-long-line + "mIsBound")); + Assert.assertTrue( + fieldExists( + "org/chromium/chrome/browser/toolbar/adaptive/settings/RadioButtonGroupAdaptiveToolbarPreference", // presubmit: ignore-long-line + "mAutoButton")); + Assert.assertTrue( + fieldExists( + "org/chromium/chrome/browser/toolbar/adaptive/settings/RadioButtonGroupAdaptiveToolbarPreference", // presubmit: ignore-long-line + "mNewTabButton")); + Assert.assertTrue( + fieldExists( + "org/chromium/chrome/browser/toolbar/adaptive/settings/RadioButtonGroupAdaptiveToolbarPreference", // presubmit: ignore-long-line + "mShareButton")); + Assert.assertTrue( + fieldExists( + "org/chromium/chrome/browser/ui/AdaptiveToolbarUiCoordinator", "mContext")); + Assert.assertTrue( + fieldExists( + "org/chromium/chrome/browser/ui/AdaptiveToolbarUiCoordinator", + "mActivityTabProvider")); + Assert.assertTrue( + fieldExists( + "org/chromium/chrome/browser/ui/AdaptiveToolbarUiCoordinator", + "mModalDialogManagerSupplier")); + Assert.assertTrue( + fieldExists( + "org/chromium/chrome/browser/ui/AdaptiveToolbarUiCoordinator", + "mProfileSupplier")); + Assert.assertTrue( + fieldExists( + "org/chromium/chrome/browser/ui/AdaptiveToolbarUiCoordinator", + "mAdaptiveToolbarButtonController")); } @Test @@ -2605,6 +2654,10 @@ public class BytecodeTest { checkSuperName( "org/chromium/chrome/browser/ui/appmenu/BraveAppMenu", "org/chromium/chrome/browser/ui/appmenu/AppMenu")); + Assert.assertTrue( + checkSuperName( + "org/chromium/chrome/browser/toolbar/adaptive/BraveAdaptiveToolbarStatePredictor", // presubmit: ignore-long-line + "org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarStatePredictor")); // presubmit: ignore-long-line } @Test diff --git a/android/junit/src/org/chromium/chrome/browser/toolbar/adaptive/settings/BraveAdaptiveToolbarSettingsFragmentTest.java b/android/junit/src/org/chromium/chrome/browser/toolbar/adaptive/settings/BraveAdaptiveToolbarSettingsFragmentTest.java index 10bfee9c89c..bcea5e8df0b 100644 --- a/android/junit/src/org/chromium/chrome/browser/toolbar/adaptive/settings/BraveAdaptiveToolbarSettingsFragmentTest.java +++ b/android/junit/src/org/chromium/chrome/browser/toolbar/adaptive/settings/BraveAdaptiveToolbarSettingsFragmentTest.java @@ -192,6 +192,30 @@ public class BraveAdaptiveToolbarSettingsFragmentTest { AdaptiveToolbarButtonVariant.VOICE, ChromeSharedPreferences.getInstance() .readInt(ADAPTIVE_TOOLBAR_CUSTOMIZATION_SETTINGS)); + + // Check indexes of Bookmarks button (as a first Brave-specific button) and + // MAX_VALUE + Assert.assertEquals( + AdaptiveToolbarButtonVariant.BOOKMARKS, + AdaptiveToolbarButtonVariant.TAB_GROUPING + 1); + Assert.assertEquals( + AdaptiveToolbarButtonVariant.NEWS, + AdaptiveToolbarButtonVariant.MAX_VALUE); + + // Test Bookmarks button + Assert.assertEquals( + R.id.adaptive_option_bookmarks, + getButton(AdaptiveToolbarButtonVariant.BOOKMARKS).getId()); + selectButton(AdaptiveToolbarButtonVariant.BOOKMARKS); + assertButtonCheckedCorrectly( + "Bookmarks", AdaptiveToolbarButtonVariant.BOOKMARKS); + Assert.assertEquals( + AdaptiveToolbarButtonVariant.BOOKMARKS, + mRadioPreference.getSelection()); + Assert.assertEquals( + AdaptiveToolbarButtonVariant.BOOKMARKS, + ChromeSharedPreferences.getInstance() + .readInt(ADAPTIVE_TOOLBAR_CUSTOMIZATION_SETTINGS)); }); } diff --git a/browser/ui/android/toolbar/BUILD.gn b/browser/ui/android/toolbar/BUILD.gn index 404bf81a754..cdc08259672 100644 --- a/browser/ui/android/toolbar/BUILD.gn +++ b/browser/ui/android/toolbar/BUILD.gn @@ -10,7 +10,11 @@ import("//extensions/buildflags/buildflags.gni") import("//third_party/jni_zero/jni_zero.gni") android_library("java") { - sources = [ "java/src/org/chromium/chrome/browser/toolbar/BraveToolbarLongPressMenuHandler.java" ] + sources = [ + "java/src/org/chromium/chrome/browser/toolbar/BraveToolbarLongPressMenuHandler.java", + "java/src/org/chromium/chrome/browser/toolbar/adaptive/BraveAdaptiveToolbarStatePredictor.java", + "java/src/org/chromium/chrome/browser/toolbar/adaptive/BraveAdaptiveToolbarStatePredictorDummySuper.java", + ] deps = [ "//base:supplier_java", @@ -21,3 +25,7 @@ android_library("java") { "//url:url_java", ] } + +android_resources("java_resources") { + sources = [ "java/brave-res/layout/brave_adaptive_preference_buttons.xml" ] +} diff --git a/browser/ui/android/toolbar/java/brave-res/layout/brave_adaptive_preference_buttons.xml b/browser/ui/android/toolbar/java/brave-res/layout/brave_adaptive_preference_buttons.xml new file mode 100644 index 00000000000..bda4a49a95f --- /dev/null +++ b/browser/ui/android/toolbar/java/brave-res/layout/brave_adaptive_preference_buttons.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/BraveAdaptiveToolbarStatePredictor.java b/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/BraveAdaptiveToolbarStatePredictor.java new file mode 100644 index 00000000000..c7cd21b56e2 --- /dev/null +++ b/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/BraveAdaptiveToolbarStatePredictor.java @@ -0,0 +1,49 @@ +/* Copyright (c) 2025 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.toolbar.adaptive; + +import android.content.Context; + +import org.chromium.build.annotations.NullMarked; +import org.chromium.build.annotations.Nullable; +import org.chromium.chrome.browser.profiles.Profile; +import org.chromium.ui.permissions.AndroidPermissionDelegate; + +/** + * Brave-specific implementation of AdaptiveToolbarStatePredictor that extends the base + * functionality to support Brave-specific toolbar button variants. + */ +@NullMarked +public class BraveAdaptiveToolbarStatePredictor + extends BraveAdaptiveToolbarStatePredictorDummySuper { + + public BraveAdaptiveToolbarStatePredictor( + Context context, + Profile profile, + @Nullable AndroidPermissionDelegate androidPermissionDelegate, + @Nullable AdaptiveToolbarBehavior behavior) { + super(context, profile, androidPermissionDelegate, behavior); + } + + /** + * Returns true if the given toolbar button variant is a Brave-specific valid option, otherwise + * defers to the parent implementation. + * + * @param variant The button variant to validate + * @return true if the variant is valid for Brave, otherwise call super implementation + */ + @Override + public boolean isValidSegment(@AdaptiveToolbarButtonVariant int variant) { + // Check Brave-specific variants first + switch (variant) { + // Add more Brave-specific variants here as needed + case AdaptiveToolbarButtonVariant.BOOKMARKS: + return true; + } + + return super.isValidSegment(variant); + } +} diff --git a/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/BraveAdaptiveToolbarStatePredictorDummySuper.java b/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/BraveAdaptiveToolbarStatePredictorDummySuper.java new file mode 100644 index 00000000000..b80e2b7ce09 --- /dev/null +++ b/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/BraveAdaptiveToolbarStatePredictorDummySuper.java @@ -0,0 +1,35 @@ +/* Copyright (c) 2025 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.toolbar.adaptive; + +import android.content.Context; + +import org.chromium.build.annotations.NullMarked; +import org.chromium.build.annotations.Nullable; +import org.chromium.chrome.browser.profiles.Profile; +import org.chromium.ui.permissions.AndroidPermissionDelegate; + +/** + * Dummy super class for BraveAdaptiveToolbarStatePredictor that allows to call the private methods + * of AdaptiveToolbarStatePredictor via bytecode modification. + */ +@NullMarked +public class BraveAdaptiveToolbarStatePredictorDummySuper extends AdaptiveToolbarStatePredictor { + public BraveAdaptiveToolbarStatePredictorDummySuper( + Context context, + Profile profile, + @Nullable AndroidPermissionDelegate androidPermissionDelegate, + @Nullable AdaptiveToolbarBehavior behavior) { + super(context, profile, androidPermissionDelegate, behavior); + + assert false : "This class usage should be removed via bytecode modification!"; + } + + public boolean isValidSegment(@AdaptiveToolbarButtonVariant int variant) { + assert false : "This class usage should be removed via bytecode modification!"; + return false; + } +} diff --git a/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/settings/BraveRadioButtonGroupAdaptiveToolbarPreference.java b/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/settings/BraveRadioButtonGroupAdaptiveToolbarPreference.java index 188daeabe2f..b850a3b2460 100644 --- a/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/settings/BraveRadioButtonGroupAdaptiveToolbarPreference.java +++ b/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/settings/BraveRadioButtonGroupAdaptiveToolbarPreference.java @@ -15,6 +15,7 @@ import androidx.preference.PreferenceViewHolder; import org.chromium.build.annotations.NullMarked; import org.chromium.build.annotations.Nullable; import org.chromium.chrome.browser.toolbar.R; +import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarButtonVariant; import org.chromium.components.browser_ui.widget.RadioButtonWithDescription; import org.chromium.ui.base.DeviceFormFactor; @@ -22,11 +23,18 @@ import org.chromium.ui.base.DeviceFormFactor; @NullMarked public class BraveRadioButtonGroupAdaptiveToolbarPreference extends RadioButtonGroupAdaptiveToolbarPreference { - private final Context mContext; + // Variables below are to be removed in the bytecode, variables from the parent class will be + // used instead. + private @AdaptiveToolbarButtonVariant int mSelected; + private boolean mIsBound; private @Nullable RadioButtonWithDescription mAutoButton; private @Nullable RadioButtonWithDescription mNewTabButton; private @Nullable RadioButtonWithDescription mShareButton; + // Own members. + private final Context mContext; + private @Nullable RadioButtonWithDescription mBookmarksButton; + public BraveRadioButtonGroupAdaptiveToolbarPreference(Context context, AttributeSet attrs) { super(context, attrs); @@ -35,21 +43,24 @@ public class BraveRadioButtonGroupAdaptiveToolbarPreference @Override public void onBindViewHolder(PreferenceViewHolder holder) { + // Needs to be done before super is called, otherwise the button will be null in getButton + // method. + mBookmarksButton = + (RadioButtonWithDescription) holder.findViewById(R.id.adaptive_option_bookmarks); + super.onBindViewHolder(holder); - mAutoButton = - (RadioButtonWithDescription) - holder.findViewById(R.id.adaptive_option_based_on_usage); // We don't have Auto option in Brave, so we hide it. - mAutoButton.setVisibility(View.GONE); - - mNewTabButton = - (RadioButtonWithDescription) holder.findViewById(R.id.adaptive_option_new_tab); - mShareButton = (RadioButtonWithDescription) holder.findViewById(R.id.adaptive_option_share); + assert mAutoButton != null : "mAutoButton should not be null at this point"; + if (mAutoButton != null) { + mAutoButton.setVisibility(View.GONE); + } } @Override public void onCheckedChanged(@Nullable RadioGroup group, int checkedId) { + if (!mIsBound) return; + RadioButtonWithDescription defaultButton = DeviceFormFactor.isNonMultiDisplayContextOnTablet(mContext) ? mShareButton @@ -60,6 +71,26 @@ public class BraveRadioButtonGroupAdaptiveToolbarPreference defaultButton.setChecked(mAutoButton.isChecked()); } + boolean isOnCheckedChangedHandled = false; + if (mBookmarksButton != null && mBookmarksButton.isChecked()) { + mSelected = AdaptiveToolbarButtonVariant.BOOKMARKS; + isOnCheckedChangedHandled = true; + } + if (isOnCheckedChangedHandled) { + callChangeListener(mSelected); + return; + } + super.onCheckedChanged(group, checkedId); } + + @Override + @Nullable RadioButtonWithDescription getButton(@AdaptiveToolbarButtonVariant int variant) { + switch (variant) { + case AdaptiveToolbarButtonVariant.BOOKMARKS: + return mBookmarksButton; + } + + return super.getButton(variant); + } } diff --git a/build/android/bytecode/BUILD.gn b/build/android/bytecode/BUILD.gn index 0e049a946d7..2b270ff2346 100644 --- a/build/android/bytecode/BUILD.gn +++ b/build/android/bytecode/BUILD.gn @@ -11,6 +11,8 @@ java_binary("java_bytecode_rewriter") { sources = [ "//brave/build/android/bytecode/java/org/brave/bytecode/BraveActivityClassAdapter.java", "//brave/build/android/bytecode/java/org/brave/bytecode/BraveAdaptiveToolbarPrefsClassAdapter.java", + "//brave/build/android/bytecode/java/org/brave/bytecode/BraveAdaptiveToolbarStatePredictorClassAdapter.java", + "//brave/build/android/bytecode/java/org/brave/bytecode/BraveAdaptiveToolbarUiCoordinatorClassAdapter.java", "//brave/build/android/bytecode/java/org/brave/bytecode/BraveAppHooksClassAdapter.java", "//brave/build/android/bytecode/java/org/brave/bytecode/BraveAppMenuClassAdapter.java", "//brave/build/android/bytecode/java/org/brave/bytecode/BraveApplicationImplBaseClassAdapter.java", @@ -101,6 +103,7 @@ java_binary("java_bytecode_rewriter") { "//brave/build/android/bytecode/java/org/brave/bytecode/BravePreferenceFragmentClassAdapter.java", "//brave/build/android/bytecode/java/org/brave/bytecode/BravePureJavaExceptionReporterClassAdapter.java", "//brave/build/android/bytecode/java/org/brave/bytecode/BraveQuickActionSearchWidgetProviderClassAdapter.java", + "//brave/build/android/bytecode/java/org/brave/bytecode/BraveRadioButtonGroupAdaptiveToolbarPreferenceClassAdapter.java", "//brave/build/android/bytecode/java/org/brave/bytecode/BraveRadioButtonGroupHomepagePreferenceClassAdapter.java", "//brave/build/android/bytecode/java/org/brave/bytecode/BraveReaderModeManagerClassAdapter.java", "//brave/build/android/bytecode/java/org/brave/bytecode/BraveReturnToChromeUtilClassAdapter.java", diff --git a/build/android/bytecode/bytecode_rewriter.gni b/build/android/bytecode/bytecode_rewriter.gni index 52d186a6fee..ecde8e8be3c 100644 --- a/build/android/bytecode/bytecode_rewriter.gni +++ b/build/android/bytecode/bytecode_rewriter.gni @@ -15,6 +15,7 @@ brave_bytecode_jars = [ "obj/brave/browser/ui/android/logo/java.javac.jar", "obj/brave/browser/ui/android/omnibox/java.javac.jar", "obj/brave/browser/ui/android/theme/java.javac.jar", + "obj/brave/browser/ui/android/toolbar/java.javac.jar", "obj/brave/components/browser_ui/notifications/android/java.javac.jar", "obj/brave/components/browser_ui/media/android/java.javac.jar", "obj/brave/components/browser_ui/site_settings/android/java.javac.jar", diff --git a/build/android/bytecode/java/org/brave/bytecode/BraveAdaptiveToolbarStatePredictorClassAdapter.java b/build/android/bytecode/java/org/brave/bytecode/BraveAdaptiveToolbarStatePredictorClassAdapter.java new file mode 100644 index 00000000000..74139e800c8 --- /dev/null +++ b/build/android/bytecode/java/org/brave/bytecode/BraveAdaptiveToolbarStatePredictorClassAdapter.java @@ -0,0 +1,25 @@ +/* Copyright (c) 2025 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.brave.bytecode; + +import org.objectweb.asm.ClassVisitor; + +public class BraveAdaptiveToolbarStatePredictorClassAdapter extends BraveClassVisitor { + static String sAdaptiveToolbarStatePredictor = + "org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarStatePredictor"; + static String sBraveAdaptiveToolbarStatePredictor = + "org/chromium/chrome/browser/toolbar/adaptive/BraveAdaptiveToolbarStatePredictor"; + + public BraveAdaptiveToolbarStatePredictorClassAdapter(ClassVisitor visitor) { + super(visitor); + + changeSuperName(sBraveAdaptiveToolbarStatePredictor, sAdaptiveToolbarStatePredictor); + + makePublicMethod(sAdaptiveToolbarStatePredictor, "isValidSegment"); + + redirectConstructor(sAdaptiveToolbarStatePredictor, sBraveAdaptiveToolbarStatePredictor); + } +} diff --git a/build/android/bytecode/java/org/brave/bytecode/BraveAdaptiveToolbarUiCoordinatorClassAdapter.java b/build/android/bytecode/java/org/brave/bytecode/BraveAdaptiveToolbarUiCoordinatorClassAdapter.java new file mode 100644 index 00000000000..6bef59b8e97 --- /dev/null +++ b/build/android/bytecode/java/org/brave/bytecode/BraveAdaptiveToolbarUiCoordinatorClassAdapter.java @@ -0,0 +1,36 @@ +/* Copyright (c) 2025 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.brave.bytecode; + +import org.objectweb.asm.ClassVisitor; + +public class BraveAdaptiveToolbarUiCoordinatorClassAdapter extends BraveClassVisitor { + static String sAdaptiveToolbarUiCoordinator = + "org/chromium/chrome/browser/ui/AdaptiveToolbarUiCoordinator"; + static String sBraveAdaptiveToolbarUiCoordinator = + "org/chromium/chrome/browser/ui/BraveAdaptiveToolbarUiCoordinator"; + + public BraveAdaptiveToolbarUiCoordinatorClassAdapter(ClassVisitor visitor) { + super(visitor); + + redirectConstructor(sAdaptiveToolbarUiCoordinator, sBraveAdaptiveToolbarUiCoordinator); + + deleteField(sBraveAdaptiveToolbarUiCoordinator, "mContext"); + makeProtectedField(sAdaptiveToolbarUiCoordinator, "mContext"); + + deleteField(sBraveAdaptiveToolbarUiCoordinator, "mActivityTabProvider"); + makeProtectedField(sAdaptiveToolbarUiCoordinator, "mActivityTabProvider"); + + deleteField(sBraveAdaptiveToolbarUiCoordinator, "mModalDialogManagerSupplier"); + makeProtectedField(sAdaptiveToolbarUiCoordinator, "mModalDialogManagerSupplier"); + + deleteField(sBraveAdaptiveToolbarUiCoordinator, "mProfileSupplier"); + makeProtectedField(sAdaptiveToolbarUiCoordinator, "mProfileSupplier"); + + deleteField(sBraveAdaptiveToolbarUiCoordinator, "mAdaptiveToolbarButtonController"); + makeProtectedField(sAdaptiveToolbarUiCoordinator, "mAdaptiveToolbarButtonController"); + } +} diff --git a/build/android/bytecode/java/org/brave/bytecode/BraveClassAdapter.java b/build/android/bytecode/java/org/brave/bytecode/BraveClassAdapter.java index 35d52b6b2af..cf31d57715b 100644 --- a/build/android/bytecode/java/org/brave/bytecode/BraveClassAdapter.java +++ b/build/android/bytecode/java/org/brave/bytecode/BraveClassAdapter.java @@ -12,6 +12,8 @@ public class BraveClassAdapter { public static ClassVisitor createAdapter(ClassVisitor chain) { chain = new BraveActivityClassAdapter(chain); chain = new BraveAdaptiveToolbarPrefsClassAdapter(chain); + chain = new BraveAdaptiveToolbarStatePredictorClassAdapter(chain); + chain = new BraveAdaptiveToolbarUiCoordinatorClassAdapter(chain); chain = new BraveAppHooksClassAdapter(chain); chain = new BraveAppMenuClassAdapter(chain); chain = new BraveBaseCustomTabActivityClassAdapter(chain); @@ -38,6 +40,7 @@ public class BraveClassAdapter { chain = new BraveChromeContextMenuPopulatorAdapter(chain); chain = new BraveQuickActionSearchWidgetProviderClassAdapter(chain); chain = new BraveRadioButtonGroupHomepagePreferenceClassAdapter(chain); + chain = new BraveRadioButtonGroupAdaptiveToolbarPreferenceClassAdapter(chain); chain = new BraveClearBrowsingDataFragmentAdapter(chain); chain = new BraveCommandLineInitUtilClassAdapter(chain); chain = new BraveContentSettingsResourcesClassAdapter(chain); diff --git a/build/android/bytecode/java/org/brave/bytecode/BraveRadioButtonGroupAdaptiveToolbarPreferenceClassAdapter.java b/build/android/bytecode/java/org/brave/bytecode/BraveRadioButtonGroupAdaptiveToolbarPreferenceClassAdapter.java new file mode 100644 index 00000000000..c50bf8758fd --- /dev/null +++ b/build/android/bytecode/java/org/brave/bytecode/BraveRadioButtonGroupAdaptiveToolbarPreferenceClassAdapter.java @@ -0,0 +1,34 @@ +/* Copyright (c) 2025 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.brave.bytecode; + +import org.objectweb.asm.ClassVisitor; + +public class BraveRadioButtonGroupAdaptiveToolbarPreferenceClassAdapter extends BraveClassVisitor { + static String sRadioButtonGroupAdaptiveToolbarPreference = + "org/chromium/chrome/browser/toolbar/adaptive/settings/RadioButtonGroupAdaptiveToolbarPreference"; // presubmit: ignore-long-line + static String sBraveRadioButtonGroupAdaptiveToolbarPreference = + "org/chromium/chrome/browser/toolbar/adaptive/settings/BraveRadioButtonGroupAdaptiveToolbarPreference"; // presubmit: ignore-long-line + + public BraveRadioButtonGroupAdaptiveToolbarPreferenceClassAdapter(ClassVisitor visitor) { + super(visitor); + + deleteField(sBraveRadioButtonGroupAdaptiveToolbarPreference, "mSelected"); + makeProtectedField(sRadioButtonGroupAdaptiveToolbarPreference, "mSelected"); + + deleteField(sBraveRadioButtonGroupAdaptiveToolbarPreference, "mIsBound"); + makeProtectedField(sRadioButtonGroupAdaptiveToolbarPreference, "mIsBound"); + + deleteField(sBraveRadioButtonGroupAdaptiveToolbarPreference, "mAutoButton"); + makeProtectedField(sRadioButtonGroupAdaptiveToolbarPreference, "mAutoButton"); + + deleteField(sBraveRadioButtonGroupAdaptiveToolbarPreference, "mNewTabButton"); + makeProtectedField(sRadioButtonGroupAdaptiveToolbarPreference, "mNewTabButton"); + + deleteField(sBraveRadioButtonGroupAdaptiveToolbarPreference, "mShareButton"); + makeProtectedField(sRadioButtonGroupAdaptiveToolbarPreference, "mShareButton"); + } +} diff --git a/patches/chrome-browser-ui-android-toolbar-BUILD.gn.patch b/patches/chrome-browser-ui-android-toolbar-BUILD.gn.patch index c2ad09eda01..aec1b59ab5e 100644 --- a/patches/chrome-browser-ui-android-toolbar-BUILD.gn.patch +++ b/patches/chrome-browser-ui-android-toolbar-BUILD.gn.patch @@ -1,5 +1,5 @@ diff --git a/chrome/browser/ui/android/toolbar/BUILD.gn b/chrome/browser/ui/android/toolbar/BUILD.gn -index 6f74e4e33637ec09ebdc586ad407a2ebbbdf4af3..1cd0e8450ed03fa0f2e577cea937e992ce1f404b 100644 +index 6f74e4e33637ec09ebdc586ad407a2ebbbdf4af3..c240048d0741ab51061acc45b54a2ffa5255c0f3 100644 --- a/chrome/browser/ui/android/toolbar/BUILD.gn +++ b/chrome/browser/ui/android/toolbar/BUILD.gn @@ -138,6 +138,7 @@ android_library("java") { @@ -10,3 +10,11 @@ index 6f74e4e33637ec09ebdc586ad407a2ebbbdf4af3..1cd0e8450ed03fa0f2e577cea937e992 deps = [ ":java_resources", +@@ -381,6 +382,7 @@ android_resources("java_resources") { + "//chrome/browser/ui/android/theme:java_resources", + "//components/browser_ui/styles/android:java_resources", + ] ++ deps += [ "//brave/browser/ui/android/toolbar:java_resources" ] + } + + robolectric_library("junit") { diff --git a/patches/chrome-browser-ui-android-toolbar-adaptive_toolbar_enums.h.patch b/patches/chrome-browser-ui-android-toolbar-adaptive_toolbar_enums.h.patch new file mode 100644 index 00000000000..592aa1180bc --- /dev/null +++ b/patches/chrome-browser-ui-android-toolbar-adaptive_toolbar_enums.h.patch @@ -0,0 +1,21 @@ +diff --git a/chrome/browser/ui/android/toolbar/adaptive_toolbar_enums.h b/chrome/browser/ui/android/toolbar/adaptive_toolbar_enums.h +index 346d0fc2b0fb5a9dcb093814511ffb2afa90f463..e721b50f63dfb3b488015d57d0ad0d4636a56e31 100644 +--- a/chrome/browser/ui/android/toolbar/adaptive_toolbar_enums.h ++++ b/chrome/browser/ui/android/toolbar/adaptive_toolbar_enums.h +@@ -50,7 +50,15 @@ enum class AdaptiveToolbarButtonVariant { + // Tab grouping action. + kTabGrouping = 16, + +- kMaxValue = kTabGrouping, ++ // We have to use multiline patch since this enum is parsed by `java_cpp_enum.py` ++ kBookmarks = kTabGrouping + 1, ++ kHistory = kBookmarks + 1, ++ kDownloads = kHistory + 1, ++ kPlaylist = kDownloads + 1, ++ kLeo = kPlaylist + 1, ++ kWallet = kLeo + 1, ++ kNews = kWallet + 1, ++ kMaxValue = kNews, + }; + // LINT.ThenChange(/tools/metrics/histograms/enums.xml:AdaptiveToolbarButtonVariant) + diff --git a/patches/chrome-browser-ui-android-toolbar-java-res-layout-radio_button_group_adaptive_toolbar_preference.xml.patch b/patches/chrome-browser-ui-android-toolbar-java-res-layout-radio_button_group_adaptive_toolbar_preference.xml.patch new file mode 100644 index 00000000000..2fd31b496a0 --- /dev/null +++ b/patches/chrome-browser-ui-android-toolbar-java-res-layout-radio_button_group_adaptive_toolbar_preference.xml.patch @@ -0,0 +1,12 @@ +diff --git a/chrome/browser/ui/android/toolbar/java/res/layout/radio_button_group_adaptive_toolbar_preference.xml b/chrome/browser/ui/android/toolbar/java/res/layout/radio_button_group_adaptive_toolbar_preference.xml +index 64275f3ef256aed1d89513c12e08d5b9ad11b8ca..fe7e6114255c38396a3977e4e6cbfc1d63efd22d 100644 +--- a/chrome/browser/ui/android/toolbar/java/res/layout/radio_button_group_adaptive_toolbar_preference.xml ++++ b/chrome/browser/ui/android/toolbar/java/res/layout/radio_button_group_adaptive_toolbar_preference.xml +@@ -36,6 +36,7 @@ found in the LICENSE file. + app:iconSrc="@drawable/new_tab_icon" + app:primaryText="@string/adaptive_toolbar_button_preference_new_tab"/> + ++ +