Init exit menu item as hidden (#34482)

Initializes exit menu item as hidden for new users only by setting it as disabled during onboarding. Exit item will be shown as disabled in the customized menu and tapping it on the toggle will bring it back.
This commit is contained in:
Simone Arpe
2026-03-09 15:19:19 +01:00
committed by GitHub
parent db800648ed
commit 9ff0e5646d
3 changed files with 115 additions and 8 deletions
@@ -52,6 +52,7 @@ import org.chromium.base.ApplicationStatus;
import org.chromium.base.ApplicationStatus.ActivityStateListener;
import org.chromium.base.BravePreferenceKeys;
import org.chromium.base.Log;
import org.chromium.brave.browser.customize_menu.CustomizeBraveMenu;
import org.chromium.build.annotations.NullMarked;
import org.chromium.build.annotations.Nullable;
import org.chromium.chrome.R;
@@ -234,6 +235,7 @@ public class WelcomeOnboardingActivity extends FirstRunActivityBase
ChromeSharedPreferences.getInstance()
.writeBoolean(OnboardingPrefManager.SHOULD_SHOW_SEARCH_WIDGET_PROMO, true);
}
CustomizeBraveMenu.initDefaultInvisibleItems(getResources());
OnboardingPrefManager.getInstance().setP3aOnboardingShown(true);
FirstRunStatus.setFirstRunFlowComplete(true);
@@ -22,6 +22,7 @@ import androidx.annotation.DrawableRes;
import androidx.annotation.IdRes;
import androidx.annotation.Nullable;
import androidx.annotation.Size;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.content.ContextCompat;
import androidx.preference.Preference;
@@ -88,6 +89,9 @@ public class CustomizeBraveMenu {
public static final int PREFERENCE_MENU_ICON_SIZE_DP = 24;
// Menu items initialized as hidden unless an explicit user preference already exists.
private static final int[] MENU_IDS_INVISIBLE_BY_DEFAULT = {R.id.exit_id};
/**
* Static mapping of menu item IDs to their corresponding drawable resource IDs. Uses
* SparseIntArray for optimal performance and memory efficiency on Android when mapping resource
@@ -297,10 +301,10 @@ public class CustomizeBraveMenu {
* @param itemId the resource ID of the menu item to check
* @return {@code true} if the item should be visible, {@code false} if it should be hidden
*/
public static boolean isVisible(final Resources resource, final int itemId) {
public static boolean isVisible(final Resources resources, final int itemId) {
String resourceName;
try {
resourceName = resource.getResourceEntryName(itemId);
resourceName = resources.getResourceEntryName(itemId);
} catch (Resources.NotFoundException notFoundException) {
assert false : "Resource not found for item with ID " + itemId;
// We are referencing a resource that does not
@@ -309,12 +313,57 @@ public class CustomizeBraveMenu {
return true;
}
return ChromeSharedPreferences.getInstance()
.readBoolean(
String.format(
Locale.ENGLISH,
CUSTOMIZABLE_BRAVE_MENU_ITEM_ID_FORMAT,
resourceName),
true);
.readBoolean(getFormattedSharedPreferenceMenuItemName(resourceName), true);
}
/**
* Initializes menu items in the {@link MENU_IDS_INVISIBLE_BY_DEFAULT} array as invisible by
* default. This method should be called once during onboarding process.
*
* @see org.chromium.chrome.browser.firstrun.WelcomeOnboardingActivity
* @param resources the resources used to access the entry name of a given menu item ID
*/
public static void initDefaultInvisibleItems(final Resources resources) {
for (final int menuItemId : MENU_IDS_INVISIBLE_BY_DEFAULT) {
initAsInvisible(resources, menuItemId);
}
}
/**
* Initializes a menu item as invisible in the user preferences. This method is a no-op when a
* preference already exists.
*
* @param resources the resources used to access the entry name of a given menu item ID
* @param itemId the resource ID of the menu item to initialize as invisible
*/
@VisibleForTesting
static void initAsInvisible(final Resources resources, final int itemId) {
String resourceName;
try {
resourceName = resources.getResourceEntryName(itemId);
} catch (Resources.NotFoundException notFoundException) {
assert false : "Resource not found for item with ID " + itemId;
// We are referencing a resource that does not
// exist. This should never happen.
// Leave visibility unchanged by returning.
return;
}
if (!ChromeSharedPreferences.getInstance()
.contains(getFormattedSharedPreferenceMenuItemName(resourceName))) {
ChromeSharedPreferences.getInstance()
.writeBoolean(getFormattedSharedPreferenceMenuItemName(resourceName), false);
}
}
/**
* Builds the shared-preference key used to persist visibility state for a menu item.
*
* @param resourceName the Android resource entry name for the menu item ID
* @return the formatted shared-preference key for that menu item
*/
private static String getFormattedSharedPreferenceMenuItemName(final String resourceName) {
return String.format(Locale.ENGLISH, CUSTOMIZABLE_BRAVE_MENU_ITEM_ID_FORMAT, resourceName);
}
/**
@@ -61,6 +61,7 @@ public class CustomizeBraveMenuTest {
mContext = ApplicationProvider.getApplicationContext();
mModelList = new MVCListAdapter.ModelList();
mResources = ApplicationProvider.getApplicationContext().getResources();
ChromeSharedPreferences.getInstance().removeKey(getMenuItemPrefKey(R.id.exit_id));
}
@Test
@@ -97,6 +98,33 @@ public class CustomizeBraveMenuTest {
mModelList.get(0).model.get(AppMenuItemProperties.MENU_ITEM_ID));
}
@Test
public void testApplyCustomization_ExitIsInvisibleByDefault() {
PropertyModel visibleItem =
new PropertyModel.Builder(AppMenuItemProperties.ALL_KEYS)
.with(AppMenuItemProperties.MENU_ITEM_ID, R.id.new_tab_menu_id)
.with(AppMenuItemProperties.TITLE, "New Tab")
.build();
PropertyModel exitItem =
new PropertyModel.Builder(AppMenuItemProperties.ALL_KEYS)
.with(AppMenuItemProperties.MENU_ITEM_ID, R.id.exit_id)
.with(AppMenuItemProperties.TITLE, "Exit")
.build();
mModelList.add(
new MVCListAdapter.ListItem(AppMenuHandler.AppMenuItemType.STANDARD, visibleItem));
mModelList.add(
new MVCListAdapter.ListItem(AppMenuHandler.AppMenuItemType.STANDARD, exitItem));
CustomizeBraveMenu.applyCustomization(mResources, mModelList);
assertEquals(1, mModelList.size());
assertEquals(
R.id.new_tab_menu_id,
mModelList.get(0).model.get(AppMenuItemProperties.MENU_ITEM_ID));
assertFalse(CustomizeBraveMenu.isVisible(mResources, R.id.exit_id));
}
@Test
public void testApplyCustomization_PreservesCustomizeAndSettingsItems() {
// Add customize menu and settings items (should not be removed).
@@ -315,6 +343,28 @@ public class CustomizeBraveMenuTest {
assertFalse(CustomizeBraveMenu.isVisible(mResources, R.id.downloads_menu_id));
}
@Test
public void testInitAsInvisible_SetsFalse_WhenPreferenceIsMissing() {
String key = getMenuItemPrefKey(R.id.exit_id);
ChromeSharedPreferences.getInstance().removeKey(key);
assertFalse(ChromeSharedPreferences.getInstance().contains(key));
CustomizeBraveMenu.initAsInvisible(mResources, R.id.exit_id);
assertTrue(ChromeSharedPreferences.getInstance().contains(key));
assertFalse(ChromeSharedPreferences.getInstance().readBoolean(key, true));
}
@Test
public void testInitAsInvisible_DoesNotOverrideExistingPreference() {
String key = getMenuItemPrefKey(R.id.exit_id);
ChromeSharedPreferences.getInstance().writeBoolean(key, true);
CustomizeBraveMenu.initAsInvisible(mResources, R.id.exit_id);
assertTrue(ChromeSharedPreferences.getInstance().readBoolean(key, false));
}
@Test
public void testGetDrawableResFromMenuItemId_ValidIds() {
// Test common menu items.
@@ -422,4 +472,10 @@ public class CustomizeBraveMenuTest {
assertEquals(AppMenuHandler.AppMenuItemType.DIVIDER, mModelList.get(3).type);
assertEquals(AppMenuHandler.AppMenuItemType.STANDARD, mModelList.get(4).type);
}
private String getMenuItemPrefKey(int menuItemId) {
return String.format(
BravePreferenceKeys.CUSTOMIZABLE_BRAVE_MENU_ITEM_ID_FORMAT,
mResources.getResourceEntryName(menuItemId));
}
}