diff --git a/extensions/spotify/src/main/java/app/revanced/extension/spotify/layout/hide/createbutton/HideCreateButtonPatch.java b/extensions/spotify/src/main/java/app/revanced/extension/spotify/layout/hide/createbutton/HideCreateButtonPatch.java index 6c0975a90..19685e31a 100644 --- a/extensions/spotify/src/main/java/app/revanced/extension/spotify/layout/hide/createbutton/HideCreateButtonPatch.java +++ b/extensions/spotify/src/main/java/app/revanced/extension/spotify/layout/hide/createbutton/HideCreateButtonPatch.java @@ -3,23 +3,29 @@ package app.revanced.extension.spotify.layout.hide.createbutton; import java.util.List; import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.Utils; +import app.revanced.extension.spotify.shared.ComponentFilters.*; @SuppressWarnings("unused") public final class HideCreateButtonPatch { /** - * A list of ids of resources which contain the Create button title. + * A list of component filters that match whether a navigation bar item is the Create button. + * The main approach used is matching the resource id for the Create button title. */ - private static final List CREATE_BUTTON_TITLE_RES_ID_LIST = List.of( - Integer.toString(Utils.getResourceIdentifier("navigationbar_musicappitems_create_title", "string")) + private static final List CREATE_BUTTON_COMPONENT_FILTERS = List.of( + new ResourceIdComponentFilter("navigationbar_musicappitems_create_title", "string"), + // Temporary fallback and fix for APKs merged with AntiSplit-M not having resources properly encoded, + // and thus getting the resource identifier for the Create button title always return 0. + // FIXME: Remove this once the above issue is no longer relevant. + new StringComponentFilter("spotify:create-menu") ); /** - * The old id of the resource which contained the Create button title. Used in older versions of the app. + * A component filter for the old id of the resource which contained the Create button title. + * Used in older versions of the app. */ - private static final int OLD_CREATE_BUTTON_TITLE_RES_ID = - Utils.getResourceIdentifier("bottom_navigation_bar_create_tab_title", "string"); + private static final ResourceIdComponentFilter OLD_CREATE_BUTTON_COMPONENT_FILTER = + new ResourceIdComponentFilter("bottom_navigation_bar_create_tab_title", "string"); /** * Injection point. This method is called on every navigation bar item to check whether it is the Create button. @@ -33,28 +39,20 @@ public final class HideCreateButtonPatch { String stringifiedNavigationBarItem = navigationBarItem.toString(); - boolean isCreateButton = false; - String matchedTitleResId = null; - - for (String titleResId : CREATE_BUTTON_TITLE_RES_ID_LIST) { - // In case the resource id has not been found. - if (titleResId.equals("0")) { + for (ComponentFilter componentFilter : CREATE_BUTTON_COMPONENT_FILTERS) { + if (componentFilter.filterUnavailable()) { + Logger.printInfo(() -> "returnNullIfIsCreateButton: Filter " + + componentFilter.getFilterRepresentation() + " not available, skipping"); continue; } - if (stringifiedNavigationBarItem.contains(titleResId)) { - isCreateButton = true; - matchedTitleResId = titleResId; + if (stringifiedNavigationBarItem.contains(componentFilter.getFilterValue())) { + Logger.printInfo(() -> "Hiding Create button because the navigation bar item " + navigationBarItem + + " matched the filter " + componentFilter.getFilterRepresentation()); + return null; } } - if (isCreateButton) { - String finalMatchedTitleResId = matchedTitleResId; - Logger.printInfo(() -> "Hiding Create button because the navigation bar item " + navigationBarItem + - " matched the title resource id " + finalMatchedTitleResId); - return null; - } - return navigationBarItem; } @@ -63,16 +61,15 @@ public final class HideCreateButtonPatch { * Create button. */ public static boolean isOldCreateButton(int oldNavigationBarItemTitleResId) { - // In case the resource id has not been found. - if (OLD_CREATE_BUTTON_TITLE_RES_ID == 0) { + if (OLD_CREATE_BUTTON_COMPONENT_FILTER.filterUnavailable()) { + Logger.printInfo(() -> "Skipping hiding old Create button because the resource id for " + + OLD_CREATE_BUTTON_COMPONENT_FILTER.resourceName + " is not available"); return false; } - boolean isCreateButton = oldNavigationBarItemTitleResId == OLD_CREATE_BUTTON_TITLE_RES_ID; - - if (isCreateButton) { + if (oldNavigationBarItemTitleResId == OLD_CREATE_BUTTON_COMPONENT_FILTER.getResourceId()) { Logger.printInfo(() -> "Hiding old Create button because the navigation bar item title resource id" + - " matched " + OLD_CREATE_BUTTON_TITLE_RES_ID); + " matched " + OLD_CREATE_BUTTON_COMPONENT_FILTER.getFilterRepresentation()); return true; } diff --git a/extensions/spotify/src/main/java/app/revanced/extension/spotify/misc/UnlockPremiumPatch.java b/extensions/spotify/src/main/java/app/revanced/extension/spotify/misc/UnlockPremiumPatch.java index d455ee6ac..1baf4af9f 100644 --- a/extensions/spotify/src/main/java/app/revanced/extension/spotify/misc/UnlockPremiumPatch.java +++ b/extensions/spotify/src/main/java/app/revanced/extension/spotify/misc/UnlockPremiumPatch.java @@ -3,6 +3,7 @@ package app.revanced.extension.spotify.misc; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; +import app.revanced.extension.spotify.shared.ComponentFilters.*; import com.spotify.home.evopage.homeapi.proto.Section; import java.util.Iterator; @@ -11,7 +12,6 @@ import java.util.Map; import java.util.Objects; import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.Utils; @SuppressWarnings("unused") public final class UnlockPremiumPatch { @@ -104,29 +104,22 @@ public final class UnlockPremiumPatch { ); /** - * A list of lists which contain strings that match whether a context menu item should be filtered out. - * The main approach used is matching context menu items by the id of their text resource. + * A list of lists which contain component filters that match whether a context menu item should be filtered out. + * The main approach used is matching context menu items by the id of their title resource. */ - private static final List> FILTERED_CONTEXT_MENU_ITEMS_BY_STRINGS = List.of( + private static final List> CONTEXT_MENU_ITEMS_COMPONENT_FILTERS = List.of( // "Listen to music ad-free" upsell on playlists. - List.of(getResourceIdentifier("context_menu_remove_ads")), + List.of(new ResourceIdComponentFilter("context_menu_remove_ads", "id")), // "Listen to music ad-free" upsell on albums. - List.of(getResourceIdentifier("playlist_entity_reinventfree_adsfree_context_menu_item")), + List.of(new ResourceIdComponentFilter("playlist_entity_reinventfree_adsfree_context_menu_item", "id")), // "Start a Jam" context menu item, but only filtered if the user does not have premium and the item is // being used as a Premium upsell (ad). List.of( - getResourceIdentifier("group_session_context_menu_start"), - "isPremiumUpsell=true" + new ResourceIdComponentFilter("group_session_context_menu_start", "id"), + new StringComponentFilter("isPremiumUpsell=true") ) ); - /** - * Utility method for returning resources ids as strings. - */ - private static String getResourceIdentifier(String resourceIdentifierName) { - return Integer.toString(Utils.getResourceIdentifier(resourceIdentifierName, "id")); - } - /** * Injection point. Override account attributes. */ @@ -210,32 +203,34 @@ public final class UnlockPremiumPatch { } String stringifiedContextMenuItem = contextMenuItem.toString(); - for (List stringList : FILTERED_CONTEXT_MENU_ITEMS_BY_STRINGS) { + + for (List componentFilters : CONTEXT_MENU_ITEMS_COMPONENT_FILTERS) { boolean allMatch = true; - StringBuilder matchedStrings = new StringBuilder(); + StringBuilder matchedFilterRepresentations = new StringBuilder(); - for (int i = 0; i < stringList.size(); i++) { - String string = stringList.get(i); + for (int i = 0, filterSize = componentFilters.size(); i < filterSize; i++) { + ComponentFilter componentFilter = componentFilters.get(i); - // In case the string is a resource id, and it has not been found. - if (string.equals("0")) { + if (componentFilter.filterUnavailable()) { + Logger.printInfo(() -> "isFilteredContextMenuItem: Filter " + + componentFilter.getFilterRepresentation() + " not available, skipping"); continue; } - if (!stringifiedContextMenuItem.contains(string)) { + if (!stringifiedContextMenuItem.contains(componentFilter.getFilterValue())) { allMatch = false; break; } - matchedStrings.append(string); - if (i < stringList.size() - 1) { - matchedStrings.append(", "); + matchedFilterRepresentations.append(componentFilter.getFilterRepresentation()); + if (i < filterSize - 1) { + matchedFilterRepresentations.append(", "); } } if (allMatch) { Logger.printInfo(() -> "Filtering context menu item " + stringifiedContextMenuItem + - " because the following strings matched: " + matchedStrings); + " because the following filters matched: " + matchedFilterRepresentations); return true; } } diff --git a/extensions/spotify/src/main/java/app/revanced/extension/spotify/shared/ComponentFilters.java b/extensions/spotify/src/main/java/app/revanced/extension/spotify/shared/ComponentFilters.java new file mode 100644 index 000000000..0349c713b --- /dev/null +++ b/extensions/spotify/src/main/java/app/revanced/extension/spotify/shared/ComponentFilters.java @@ -0,0 +1,79 @@ +package app.revanced.extension.spotify.shared; + +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.Utils; + +public final class ComponentFilters { + + public interface ComponentFilter { + String getFilterValue(); + String getFilterRepresentation(); + default boolean filterUnavailable() { + return false; + } + } + + public static final class ResourceIdComponentFilter implements ComponentFilter { + + public final String resourceName; + public final String resourceType; + // Android resources are always positive, so -1 is a valid sentinel value to indicate it has not been loaded. + // 0 is returned when a resource has not been found. + private int resourceId = -1; + private String stringfiedResourceId = null; + + public ResourceIdComponentFilter(String resourceName, String resourceType) { + this.resourceName = resourceName; + this.resourceType = resourceType; + } + + public int getResourceId() { + if (resourceId == -1) { + resourceId = Utils.getResourceIdentifier(resourceName, resourceType); + } + return resourceId; + } + + @Override + public String getFilterValue() { + if (stringfiedResourceId == null) { + stringfiedResourceId = Integer.toString(getResourceId()); + } + return stringfiedResourceId; + } + + @Override + public String getFilterRepresentation() { + boolean resourceFound = getResourceId() != 0; + return (resourceFound ? getFilterValue() + " (" : "") + resourceName + (resourceFound ? ")" : ""); + } + + @Override + public boolean filterUnavailable() { + boolean resourceNotFound = getResourceId() == 0; + if (resourceNotFound) { + Logger.printInfo(() -> "Resource id for " + resourceName + " was not found"); + } + return resourceNotFound; + } + } + + public static final class StringComponentFilter implements ComponentFilter { + + public final String string; + + public StringComponentFilter(String string) { + this.string = string; + } + + @Override + public String getFilterValue() { + return string; + } + + @Override + public String getFilterRepresentation() { + return string; + } + } +}