diff --git a/android/java/org/chromium/chrome/browser/app/BraveActivity.java b/android/java/org/chromium/chrome/browser/app/BraveActivity.java index dde69d9c355..6dd6f5b6e7b 100644 --- a/android/java/org/chromium/chrome/browser/app/BraveActivity.java +++ b/android/java/org/chromium/chrome/browser/app/BraveActivity.java @@ -464,17 +464,22 @@ public abstract class BraveActivity extends ChromeActivity final BraveTabbedAppMenuPropertiesDelegate braveTabbedAppMenuPropertiesDelegate = (BraveTabbedAppMenuPropertiesDelegate) delegate; - final Bundle bundle = - CustomizeBraveMenu.populateBundle( - getResources(), - new Bundle(), - braveTabbedAppMenuPropertiesDelegate.buildMainMenuModelList(), - braveTabbedAppMenuPropertiesDelegate.buildPageActionsModelList()); - SettingsNavigation settingsNavigation = - SettingsNavigationFactory.createSettingsNavigation(); - // Follow upstream code and pass null as fragment to show - // that defaults to main settings screen. - settingsNavigation.startSettings(this, null, bundle); + // Use async version to ensure policy values are checked before building menu + braveTabbedAppMenuPropertiesDelegate.buildMainMenuModelListAsync( + (mainMenuList) -> { + final Bundle bundle = + CustomizeBraveMenu.populateBundle( + getResources(), + new Bundle(), + mainMenuList, + braveTabbedAppMenuPropertiesDelegate + .buildPageActionsModelList()); + SettingsNavigation settingsNavigation = + SettingsNavigationFactory.createSettingsNavigation(); + // Follow upstream code and pass null as fragment to show + // that defaults to main settings screen. + settingsNavigation.startSettings(BraveActivity.this, null, bundle); + }); return true; } @@ -537,11 +542,15 @@ public abstract class BraveActivity extends ChromeActivity assert delegate instanceof BraveTabbedAppMenuPropertiesDelegate; final BraveTabbedAppMenuPropertiesDelegate braveTabbedAppMenuPropertiesDelegate = (BraveTabbedAppMenuPropertiesDelegate) delegate; - // Get full menu items and pass them to settings. - CustomizeBraveMenu.openCustomizeMenuSettings( - this, - braveTabbedAppMenuPropertiesDelegate.buildMainMenuModelList(), - braveTabbedAppMenuPropertiesDelegate.buildPageActionsModelList()); + // Use async version to ensure policy values are checked before building menu + braveTabbedAppMenuPropertiesDelegate.buildMainMenuModelListAsync( + (mainMenuList) -> { + // Get full menu items and pass them to settings. + CustomizeBraveMenu.openCustomizeMenuSettings( + BraveActivity.this, + mainMenuList, + braveTabbedAppMenuPropertiesDelegate.buildPageActionsModelList()); + }); } else if (id == R.id.brave_shred_id) { shredData(currentTab); } else { diff --git a/android/java/org/chromium/chrome/browser/brave_origin/BraveOriginSubscriptionPrefs.java b/android/java/org/chromium/chrome/browser/brave_origin/BraveOriginSubscriptionPrefs.java index 16e42b45299..74a526cce86 100644 --- a/android/java/org/chromium/chrome/browser/brave_origin/BraveOriginSubscriptionPrefs.java +++ b/android/java/org/chromium/chrome/browser/brave_origin/BraveOriginSubscriptionPrefs.java @@ -11,16 +11,21 @@ import android.util.Base64; import org.json.JSONException; import org.json.JSONObject; +import org.chromium.base.BraveFeatureList; import org.chromium.base.Callback; import org.chromium.base.ContextUtils; import org.chromium.base.Log; import org.chromium.base.task.PostTask; import org.chromium.base.task.TaskTraits; +import org.chromium.brave.browser.brave_origin.BraveOriginServiceFactory; import org.chromium.brave.browser.skus.SkusServiceFactory; import org.chromium.brave.browser.util.BraveDomainsUtils; import org.chromium.brave.browser.util.ServicesEnvironment; +import org.chromium.brave_origin.mojom.BraveOriginSettingsHandler; import org.chromium.build.annotations.NullMarked; import org.chromium.build.annotations.Nullable; +import org.chromium.chrome.browser.flags.ChromeFeatureList; +import org.chromium.chrome.browser.policy.BravePolicyConstants; import org.chromium.chrome.browser.preferences.BravePref; import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.settings.BraveOriginPreferences; @@ -402,4 +407,87 @@ public class BraveOriginSubscriptionPrefs { SettingsNavigationFactory.createSettingsNavigation() .startSettings(activity, BraveOriginPreferences.class); } + + /** + * Checks if a policy value should be inverted when mapping to/from UI state. DISABLED policies + * need inversion (true = disabled = unchecked in UI). ENABLED policies don't need inversion + * (true = enabled = checked in UI). + * + * @param policyKey The policy key to check + * @return true if the policy value should be inverted, false otherwise + */ + public static boolean isPolicyInverted(@Nullable String policyKey) { + if (policyKey == null) { + return false; + } + switch (policyKey) { + case BravePolicyConstants.BRAVE_REWARDS_DISABLED: + case BravePolicyConstants.BRAVE_NEWS_DISABLED: + case BravePolicyConstants.BRAVE_V_P_N_DISABLED: + case BravePolicyConstants.BRAVE_WALLET_DISABLED: + return true; + default: + return false; + } + } + + /** + * Generic method to check if a feature is disabled by policy asynchronously. This method checks + * subscription status first, then checks the policy value. + * + * @param profile The profile to use for the operation + * @param policyKey The policy key to check (e.g., BravePolicyConstants.BRAVE_REWARDS_DISABLED) + * @param callback Called with the policy value (true if disabled, false if not disabled) + */ + public static void checkPolicyAsync( + @Nullable Profile profile, String policyKey, @Nullable Callback callback) { + if (callback == null) { + return; + } + + // If Brave Origin feature is not enabled, policies are not applicable + if (!ChromeFeatureList.isEnabled(BraveFeatureList.BRAVE_ORIGIN)) { + callback.onResult(false); + return; + } + + if (profile == null) { + callback.onResult(false); + return; + } + + // Check subscription status first - if not active, policies don't apply (feature enabled) + requestCredentialSummary( + profile, + (isSubscriptionActive) -> { + // If subscription is not active, return false (feature not disabled = enabled) + if (!isSubscriptionActive) { + callback.onResult(false); + return; + } + + // Subscription is active, proceed with policy check + BraveOriginServiceFactory factory = BraveOriginServiceFactory.getInstance(); + BraveOriginSettingsHandler handler = + factory.getBraveOriginSettingsHandler(profile, null); + if (handler == null) { + callback.onResult(false); + return; + } + + handler.getPolicyValue( + policyKey, + (value) -> { + // For "DISABLED" policies (inverted): true = disabled, null/false = + // enabled + // For "ENABLED" policies: true = enabled, null/false = disabled + boolean isDisabled = + isPolicyInverted(policyKey) + ? (value != null && value) + : (value == null || !value); + callback.onResult(isDisabled); + handler.close(); + }); + }); + } } diff --git a/android/java/org/chromium/chrome/browser/settings/AppearancePreferences.java b/android/java/org/chromium/chrome/browser/settings/AppearancePreferences.java index 0fd40ce4816..d7548306534 100644 --- a/android/java/org/chromium/chrome/browser/settings/AppearancePreferences.java +++ b/android/java/org/chromium/chrome/browser/settings/AppearancePreferences.java @@ -20,12 +20,14 @@ import org.chromium.chrome.browser.BraveRelaunchUtils; import org.chromium.chrome.browser.BraveRewardsNativeWorker; import org.chromium.chrome.browser.BraveRewardsObserver; import org.chromium.chrome.browser.appearance.settings.AppearanceSettingsFragment; +import org.chromium.chrome.browser.brave_origin.BraveOriginSubscriptionPrefs; import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.multiwindow.BraveMultiWindowDialogFragment; import org.chromium.chrome.browser.multiwindow.BraveMultiWindowUtils; import org.chromium.chrome.browser.multiwindow.MultiInstanceManager.PersistedInstanceType; import org.chromium.chrome.browser.multiwindow.MultiWindowUtils; import org.chromium.chrome.browser.ntp.NtpUtil; +import org.chromium.chrome.browser.policy.BravePolicyConstants; import org.chromium.chrome.browser.preferences.ChromeSharedPreferences; import org.chromium.chrome.browser.tasks.tab_management.BraveTabUiFeatureUtilities; import org.chromium.chrome.browser.toolbar.ToolbarPositionController; @@ -175,6 +177,9 @@ public class AppearancePreferences extends AppearanceSettingsFragment } super.onStart(); + // Check if Brave Rewards is disabled by policy and hide the icon preference if so + checkRewardsPolicyAndUpdatePreference(); + if (ToolbarPositionController.isToolbarPositionCustomizationEnabled(getContext(), false)) { updatePreferenceTitle( PREF_ADDRESS_BAR, AddressBarSettingsFragment.getTitle(getContext())); @@ -346,4 +351,23 @@ public class AppearancePreferences extends AppearanceSettingsFragment preference.setOrder(order); } } + + /** + * Checks if Brave Rewards is disabled by policy via Brave Origin and removes related + * preferences if so. This ensures that when Brave Rewards is disabled, users cannot toggle + * rewards-related settings in the appearance settings. + */ + private void checkRewardsPolicyAndUpdatePreference() { + BraveOriginSubscriptionPrefs.checkPolicyAsync( + getProfile(), + BravePolicyConstants.BRAVE_REWARDS_DISABLED, + (isDisabled) -> { + if (getActivity() == null || getActivity().isFinishing() || !isDisabled) { + return; + } + // Policy disables Brave Rewards - remove rewards-related preferences + removePreferenceIfPresent(PREF_SHOW_BRAVE_REWARDS_ICON); + removePreferenceIfPresent(PREF_ADS_SWITCH); + }); + } } diff --git a/android/java/org/chromium/chrome/browser/settings/BraveOriginPreferences.java b/android/java/org/chromium/chrome/browser/settings/BraveOriginPreferences.java index 97ddbc651b3..ad4636e5a31 100644 --- a/android/java/org/chromium/chrome/browser/settings/BraveOriginPreferences.java +++ b/android/java/org/chromium/chrome/browser/settings/BraveOriginPreferences.java @@ -21,6 +21,7 @@ import org.chromium.brave_origin.mojom.BraveOriginSettingsHandler; import org.chromium.build.annotations.NullMarked; import org.chromium.build.annotations.Nullable; import org.chromium.chrome.R; +import org.chromium.chrome.browser.brave_origin.BraveOriginSubscriptionPrefs; import org.chromium.chrome.browser.policy.BravePolicyConstants; import org.chromium.chrome.browser.profiles.Profile; import org.chromium.components.browser_ui.settings.ChromeSwitchPreference; @@ -126,9 +127,13 @@ public class BraveOriginPreferences extends BravePreferenceFragment if (policyKey == null || mBraveOriginSettingsHandler == null) { return false; } + // For DISABLED policies, invert the value (checked = enabled = false policy value) + // For ENABLED policies, use the value as-is (checked = enabled = true policy value) + boolean policyValue = + BraveOriginSubscriptionPrefs.isPolicyInverted(policyKey) ? !isEnabled : isEnabled; mBraveOriginSettingsHandler.setPolicyValue( policyKey, - isEnabled, + policyValue, (success) -> { if (!success) { Log.e(TAG, "Failed to set policy value for " + policyKey); @@ -170,8 +175,14 @@ public class BraveOriginPreferences extends BravePreferenceFragment policyKey, (value) -> { if (value != null) { - preference.setChecked(value); - updateToggleDescription(preference, value); + // For DISABLED policies, invert the value (!value) + // For ENABLED policies, use the value as-is + boolean checkedValue = + BraveOriginSubscriptionPrefs.isPolicyInverted(policyKey) + ? !value + : value; + preference.setChecked(checkedValue); + updateToggleDescription(preference, checkedValue); } }); } diff --git a/android/java/org/chromium/chrome/browser/tabbed_mode/BraveTabbedAppMenuPropertiesDelegate.java b/android/java/org/chromium/chrome/browser/tabbed_mode/BraveTabbedAppMenuPropertiesDelegate.java index 12cd0751724..a02025a2544 100644 --- a/android/java/org/chromium/chrome/browser/tabbed_mode/BraveTabbedAppMenuPropertiesDelegate.java +++ b/android/java/org/chromium/chrome/browser/tabbed_mode/BraveTabbedAppMenuPropertiesDelegate.java @@ -23,7 +23,9 @@ import com.google.android.material.button.MaterialButton; import org.chromium.base.BraveFeatureList; import org.chromium.base.BravePreferenceKeys; import org.chromium.base.BraveUrlConstants; +import org.chromium.base.Callback; import org.chromium.base.DeviceInfo; +import org.chromium.base.library_loader.LibraryLoader; import org.chromium.base.supplier.ObservableSupplier; import org.chromium.base.supplier.OneshotSupplier; import org.chromium.brave.browser.customize_menu.CustomizeBraveMenu; @@ -35,6 +37,7 @@ import org.chromium.chrome.browser.BraveRewardsNativeWorker; import org.chromium.chrome.browser.app.appmenu.AppMenuIconRowFooter; import org.chromium.chrome.browser.bookmarks.BookmarkModel; import org.chromium.chrome.browser.brave_leo.BraveLeoPrefUtils; +import org.chromium.chrome.browser.brave_origin.BraveOriginSubscriptionPrefs; import org.chromium.chrome.browser.feed.webfeed.WebFeedSnackbarController; import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.homepage.HomepageManager; @@ -44,7 +47,9 @@ import org.chromium.chrome.browser.layouts.LayoutStateProvider; import org.chromium.chrome.browser.multiwindow.BraveMultiWindowUtils; import org.chromium.chrome.browser.multiwindow.MultiWindowModeStateDispatcher; import org.chromium.chrome.browser.multiwindow.MultiWindowUtils; +import org.chromium.chrome.browser.policy.BravePolicyConstants; import org.chromium.chrome.browser.preferences.ChromeSharedPreferences; +import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.readaloud.ReadAloudController; import org.chromium.chrome.browser.set_default_browser.BraveSetDefaultBrowserUtils; import org.chromium.chrome.browser.tab.Tab; @@ -73,7 +78,9 @@ import org.chromium.ui.modelutil.PropertyModel; import org.chromium.url.GURL; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.Supplier; /** Brave's extension for TabbedAppMenuPropertiesDelegate */ @@ -84,6 +91,60 @@ public class BraveTabbedAppMenuPropertiesDelegate extends TabbedAppMenuPropertie private boolean mJunitIsTesting; private final Context mBraveContext; + /** + * Represents a menu item that can be controlled by policy. + * + *

To add a new policy-controlled menu item, add a new entry to {@link + * #getPolicyControlledMenuItems()} with: - menuItemId: The resource ID of the menu item (e.g., + * R.id.brave_news_id) - policyKey: The policy constant (e.g., + * BravePolicyConstants.BRAVE_NEWS_DISABLED) - itemBuilder: Method reference to build the menu + * item (e.g., this::buildBraveNewsItem) - isSupportedChecker: Lambda to check if feature is + * supported (e.g., () -> true) - insertBeforeItemId: Menu item ID to insert before (e.g., + * R.id.brave_customize_id) + * + *

The item will automatically be hidden/shown in the app menu based on policy ({@link + * #updateMenuItemsBasedOnPolicy}) and hidden/shown in the settings screen menu based on policy + * ({@link #buildMainMenuModelListAsync}). + */ + private static class PolicyControlledMenuItem { + final int mMenuItemId; + final String mPolicyKey; + final Supplier mItemBuilder; + final Supplier mIsSupportedChecker; + final int mInsertBeforeItemId; + + PolicyControlledMenuItem( + int menuItemId, + String policyKey, + Supplier itemBuilder, + Supplier isSupportedChecker, + int insertBeforeItemId) { + mMenuItemId = menuItemId; + mPolicyKey = policyKey; + mItemBuilder = itemBuilder; + mIsSupportedChecker = isSupportedChecker; + mInsertBeforeItemId = insertBeforeItemId; + } + } + + /** + * Configuration list of all policy-controlled menu items. To add a new feature, simply add an + * entry here. + */ + private List getPolicyControlledMenuItems() { + return Arrays.asList( + new PolicyControlledMenuItem( + R.id.brave_rewards_id, + BravePolicyConstants.BRAVE_REWARDS_DISABLED, + this::buildBraveRewardsItem, + () -> { + BraveRewardsNativeWorker worker = + BraveRewardsNativeWorker.getInstance(); + return worker != null && worker.isSupported(); + }, + R.id.brave_news_id)); + } + public BraveTabbedAppMenuPropertiesDelegate( Context context, ActivityTabProvider activityTabProvider, @@ -124,6 +185,116 @@ public class BraveTabbedAppMenuPropertiesDelegate extends TabbedAppMenuPropertie mBraveContext = context; } + /** + * Helper method to get the current profile from the activity tab provider. Used for policy + * checks that require a profile. + * + * @return The profile, or null if not available + */ + @Nullable + private Profile getCurrentProfile() { + Tab currentTab = mActivityTabProvider.get(); + if (currentTab == null) { + return null; + } + // Check if native library is loaded before calling native method + // In unit tests (Robolectric), native methods are not available + if (!LibraryLoader.getInstance().isInitialized()) { + return null; + } + return Profile.fromWebContents(currentTab.getWebContents()); + } + + /** + * Checks if a menu item is present in the menu list. + * + * @param modelList The menu model list to search + * @param menuItemId The menu item ID to look for + * @return true if the item is present, false otherwise + */ + private boolean isMenuItemPresent(MVCListAdapter.ModelList modelList, int menuItemId) { + for (int i = 0; i < modelList.size(); i++) { + MVCListAdapter.ListItem item = modelList.get(i); + if (item.model.get(AppMenuItemProperties.MENU_ITEM_ID) == menuItemId) { + return true; + } + } + return false; + } + + /** + * Removes a menu item by ID from the menu list. + * + * @param modelList The menu model list to modify + * @param menuItemId The menu item ID to remove + */ + private void removeMenuItemById(MVCListAdapter.ModelList modelList, int menuItemId) { + for (int i = 0; i < modelList.size(); i++) { + MVCListAdapter.ListItem item = modelList.get(i); + if (item.model.get(AppMenuItemProperties.MENU_ITEM_ID) == menuItemId) { + modelList.removeAt(i); + break; + } + } + } + + /** + * Inserts a menu item before a specified item in the menu list. + * + * @param modelList The menu model list to modify + * @param itemToInsert The menu item to insert + * @param beforeItemId The menu item ID to insert before + */ + private void insertMenuItemBefore( + MVCListAdapter.ModelList modelList, + MVCListAdapter.ListItem itemToInsert, + int beforeItemId) { + int insertIndex = modelList.size() - 1; // Default to end if beforeItemId not found + for (int i = 0; i < modelList.size(); i++) { + MVCListAdapter.ListItem item = modelList.get(i); + if (item.model.get(AppMenuItemProperties.MENU_ITEM_ID) == beforeItemId) { + insertIndex = i; + break; + } + } + modelList.add(insertIndex, itemToInsert); + } + + /** + * Updates menu items based on policy checks for all configured policy-controlled items. This + * method checks policies asynchronously and adds/removes menu items accordingly. + * + * @param modelList The menu model list to update + */ + private void updateMenuItemsBasedOnPolicy(MVCListAdapter.ModelList modelList) { + Profile profile = getCurrentProfile(); + if (profile == null) { + // Profile not available (e.g., in tests) - skip policy check + return; + } + for (PolicyControlledMenuItem item : getPolicyControlledMenuItems()) { + BraveOriginSubscriptionPrefs.checkPolicyAsync( + profile, + item.mPolicyKey, + (isDisabled) -> { + if (isDisabled) { + // Policy disables the feature - remove item if present + removeMenuItemById(modelList, item.mMenuItemId); + } else { + // Policy allows the feature - add item if not present and supported + if (!isMenuItemPresent(modelList, item.mMenuItemId)) { + if (item.mIsSupportedChecker.get()) { + insertMenuItemBefore( + modelList, + item.mItemBuilder.get(), + item.mInsertBeforeItemId); + } + } + } + }); + } + } + private void onFooterViewInflated(AppMenuHandler appMenuHandler, View view) { // If it's still null, just hide the whole view if (mBookmarkModelSupplier.get() == null) { @@ -272,6 +443,54 @@ public class BraveTabbedAppMenuPropertiesDelegate extends TabbedAppMenuPropertie && super.shouldShowMoveToOtherWindow(); } + /** + * Builds the complete list of main menu items for the Customize menu settings screen + * asynchronously, ensuring policy values are checked before building. + * + *

This method checks all policy-controlled menu items asynchronously, then builds the menu + * list with the correct policy state. Use this method instead of {@link + * #buildMainMenuModelList()} when you need to ensure policy values are up-to-date. + * + * @param callback Called with the built menu model list once all policy checks complete + */ + public void buildMainMenuModelListAsync(Callback callback) { + // Always check policy fresh (no caching) before building menu + // Check all policy-controlled items, then build menu with policy states + Profile profile = getCurrentProfile(); + List policyItems = getPolicyControlledMenuItems(); + + if (profile == null || policyItems.isEmpty()) { + // Profile not available (e.g., in tests) or no policy items - build menu without policy + // checks + MVCListAdapter.ModelList menuList = buildMainMenuModelList(new HashMap<>()); + callback.onResult(menuList); + return; + } + + // Map to store policy states: menuItemId -> isDisabled + Map policyStates = new HashMap<>(); + final int[] pendingChecks = { + policyItems.size() + }; // Use array to allow modification in lambda + + for (PolicyControlledMenuItem item : policyItems) { + BraveOriginSubscriptionPrefs.checkPolicyAsync( + profile, + item.mPolicyKey, + (isDisabled) -> { + policyStates.put(item.mMenuItemId, isDisabled); + pendingChecks[0]--; + + // When all policy checks are complete, build the menu + if (pendingChecks[0] == 0) { + MVCListAdapter.ModelList menuList = + buildMainMenuModelList(policyStates); + callback.onResult(menuList); + } + }); + } + } + /** * Builds the complete list of main menu items for the Customize menu settings screen. * @@ -279,15 +498,21 @@ public class BraveTabbedAppMenuPropertiesDelegate extends TabbedAppMenuPropertie * customize through the menu settings. The list includes items like "New Tab", "History", * "Downloads", "Brave Wallet", etc., based on feature availability and device configuration. * + *

Note: This synchronous version defaults to showing all menu items since + * it cannot check policy asynchronously. For accurate policy-based filtering, use {@link + * #buildMainMenuModelListAsync(Callback)} instead. + * *

Note on Icons: The returned menu items do not include drawable icons * because {@link android.graphics.drawable.Drawable} objects cannot be parceled across activity * boundaries. Instead, the settings screen uses {@link * CustomizeBraveMenu#getDrawableResFromMenuItemId(int)} to map menu item IDs to their * corresponding drawable resource IDs for display. * + * @param policyStates Map of menu item IDs to their disabled-by-policy state (true = disabled, + * false/not present = enabled) * @return a ModelList containing all customizable main menu items with their IDs and titles */ - public MVCListAdapter.ModelList buildMainMenuModelList() { + private MVCListAdapter.ModelList buildMainMenuModelList(Map policyStates) { MVCListAdapter.ModelList modelList = new MVCListAdapter.ModelList(); // New Tab @@ -420,9 +645,13 @@ public class BraveTabbedAppMenuPropertiesDelegate extends TabbedAppMenuPropertie modelList.add(buildBraveVpnLocationIconItem()); } } - BraveRewardsNativeWorker braveRewardsNativeWorker = BraveRewardsNativeWorker.getInstance(); - if (braveRewardsNativeWorker != null && braveRewardsNativeWorker.isSupported()) { - modelList.add(buildBraveRewardsItem()); + // Add policy-controlled items based on policy states + for (PolicyControlledMenuItem item : getPolicyControlledMenuItems()) { + // Check if item is disabled by policy (default to false/not disabled if not in map) + boolean isDisabled = policyStates.getOrDefault(item.mMenuItemId, false); + if (!isDisabled && item.mIsSupportedChecker.get()) { + modelList.add(item.mItemBuilder.get()); + } } modelList.add(buildBraveNewsItem()); @@ -629,7 +858,11 @@ public class BraveTabbedAppMenuPropertiesDelegate extends TabbedAppMenuPropertie int menuGroup = getMenuGroup(); if (menuGroup == MenuGroup.PAGE_MENU) { + // Build menu without policy-controlled items initially (safer default) + // Policy-controlled items will be added/removed asynchronously based on policy checks populateBravePageModeMenu(modelList); + // Check policies and update menu dynamically for all configured policy-controlled items + updateMenuItemsBasedOnPolicy(modelList); } // Apply Brave icons. maybeReplaceIcons(modelList); @@ -721,12 +954,10 @@ public class BraveTabbedAppMenuPropertiesDelegate extends TabbedAppMenuPropertie modelList.add(buildBraveVpnLocationIconItem()); } } - BraveRewardsNativeWorker braveRewardsNativeWorker = - BraveRewardsNativeWorker.getInstance(); - if (braveRewardsNativeWorker != null && braveRewardsNativeWorker.isSupported()) { - modelList.add(buildBraveRewardsItem()); - } } + // Policy-controlled items (like Rewards) are handled by updateMenuItemsBasedOnPolicy() + // They are not added here to avoid showing them if policy disables them + // The async policy check will add them if policy allows modelList.add(buildBraveNewsItem()); modelList.add(buildCustomMenuItem()); modelList.add(buildExitItem()); diff --git a/android/java/org/chromium/chrome/browser/toolbar/top/BraveToolbarLayoutImpl.java b/android/java/org/chromium/chrome/browser/toolbar/top/BraveToolbarLayoutImpl.java index 6efb0f39cb8..dd17523f8e8 100644 --- a/android/java/org/chromium/chrome/browser/toolbar/top/BraveToolbarLayoutImpl.java +++ b/android/java/org/chromium/chrome/browser/toolbar/top/BraveToolbarLayoutImpl.java @@ -56,6 +56,7 @@ import org.chromium.chrome.browser.BraveRewardsHelper; import org.chromium.chrome.browser.BraveRewardsNativeWorker; import org.chromium.chrome.browser.BraveRewardsObserver; import org.chromium.chrome.browser.app.BraveActivity; +import org.chromium.chrome.browser.brave_origin.BraveOriginSubscriptionPrefs; import org.chromium.chrome.browser.brave_stats.BraveStatsUtil; import org.chromium.chrome.browser.crypto_wallet.controller.DAppsWalletController; import org.chromium.chrome.browser.custom_layout.popup_window_tooltip.PopupWindowTooltip; @@ -76,6 +77,7 @@ import org.chromium.chrome.browser.onboarding.v2.HighlightView; import org.chromium.chrome.browser.playlist.PlaylistServiceFactoryAndroid; import org.chromium.chrome.browser.playlist.PlaylistServiceObserverImpl; import org.chromium.chrome.browser.playlist.PlaylistServiceObserverImpl.PlaylistServiceObserverImplDelegate; +import org.chromium.chrome.browser.policy.BravePolicyConstants; import org.chromium.chrome.browser.preferences.ChromeSharedPreferences; import org.chromium.chrome.browser.preferences.website.BraveShieldsContentSettings; import org.chromium.chrome.browser.preferences.website.BraveShieldsContentSettingsObserver; @@ -481,18 +483,12 @@ public abstract class BraveToolbarLayoutImpl extends ToolbarLayout && mBraveRewardsNativeWorker.isSupported() && NtpUtil.shouldShowRewardsIcon() && mRewardsLayout != null) { - mRewardsLayout.setVisibility(View.VISIBLE); - } - maybeShowTermsOfServiceUpdateRequiredBadge(); - if (mShieldsLayout != null) { - updateShieldsLayoutBackground( - !(mRewardsLayout != null && mRewardsLayout.getVisibility() == View.VISIBLE)); - mShieldsLayout.setVisibility(View.VISIBLE); - } - if (mBraveRewardsNativeWorker != null) { - mBraveRewardsNativeWorker.addObserver(this); - mBraveRewardsNativeWorker.addPublisherObserver(this); - mBraveRewardsNativeWorker.getAllNotifications(); + // Check if Brave Rewards is disabled by policy before showing + checkRewardsPolicyAndUpdateToolbarButton(); + } else { + // Rewards not supported or user disabled it - complete initialization without policy + // check + completeRewardsInitialization(); } } @@ -1375,8 +1371,28 @@ public abstract class BraveToolbarLayoutImpl extends ToolbarLayout && mBraveRewardsNativeWorker != null && mBraveRewardsNativeWorker.isSupported() && NtpUtil.shouldShowRewardsIcon()) { - mRewardsLayout.setVisibility(View.VISIBLE); - updateShieldsLayoutBackground(false); + // Check policy before showing rewards icon + Profile profile = tab != null ? Profile.fromWebContents(tab.getWebContents()) : null; + BraveOriginSubscriptionPrefs.checkPolicyAsync( + profile, + BravePolicyConstants.BRAVE_REWARDS_DISABLED, + (isDisabled) -> { + Context context = getContext(); + if ((context instanceof Activity + && (((Activity) context).isFinishing() + || ((Activity) context).isDestroyed())) + || mRewardsLayout == null) { + return; + } + + if (!isDisabled) { + mRewardsLayout.setVisibility(View.VISIBLE); + updateShieldsLayoutBackground(false); + } else { + mRewardsLayout.setVisibility(View.GONE); + updateShieldsLayoutBackground(true); + } + }); } } @@ -1759,4 +1775,55 @@ public abstract class BraveToolbarLayoutImpl extends ToolbarLayout showOrHideRewardsBadge(true); } } + + /** + * Completes rewards-related initialization that should happen after policy check. This includes + * updating shields layout background and setting up rewards observers. + */ + private void completeRewardsInitialization() { + maybeShowTermsOfServiceUpdateRequiredBadge(); + if (mShieldsLayout != null) { + updateShieldsLayoutBackground( + !(mRewardsLayout != null && mRewardsLayout.getVisibility() == View.VISIBLE)); + mShieldsLayout.setVisibility(View.VISIBLE); + } + if (mBraveRewardsNativeWorker != null) { + mBraveRewardsNativeWorker.addObserver(this); + mBraveRewardsNativeWorker.addPublisherObserver(this); + mBraveRewardsNativeWorker.getAllNotifications(); + } + } + + /** + * Checks if Brave Rewards is disabled by policy via Brave Origin and updates the toolbar + * rewards button visibility accordingly. This ensures that when Brave Rewards is disabled by + * policy, the rewards icon in the toolbar is force-hidden regardless of user preference. + */ + private void checkRewardsPolicyAndUpdateToolbarButton() { + Tab currentTab = getToolbarDataProvider().getTab(); + Profile profile = + currentTab != null ? Profile.fromWebContents(currentTab.getWebContents()) : null; + + BraveOriginSubscriptionPrefs.checkPolicyAsync( + profile, + BravePolicyConstants.BRAVE_REWARDS_DISABLED, + (isDisabled) -> { + Context context = getContext(); + if ((context instanceof Activity + && (((Activity) context).isFinishing() + || ((Activity) context).isDestroyed())) + || mRewardsLayout == null) { + return; + } + + // Only show if policy allows (not disabled) + if (!isDisabled) { + mRewardsLayout.setVisibility(View.VISIBLE); + } + // If policy disables rewards, keep it hidden (default is GONE) + + // Complete the rest of initialization after policy check + completeRewardsInitialization(); + }); + } }