diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/HideCategoryBarPatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/HideCategoryBarPatch.java new file mode 100644 index 000000000..f0433ccb1 --- /dev/null +++ b/extensions/music/src/main/java/app/revanced/extension/music/patches/HideCategoryBarPatch.java @@ -0,0 +1,14 @@ +package app.revanced.extension.music.patches; + +import app.revanced.extension.music.settings.Settings; + +@SuppressWarnings("unused") +public class HideCategoryBarPatch { + + /** + * Injection point + */ + public static boolean hideCategoryBar() { + return Settings.HIDE_CATEGORY_BAR.get(); + } +} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/HideGetPremiumPatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/HideGetPremiumPatch.java new file mode 100644 index 000000000..658c0e59a --- /dev/null +++ b/extensions/music/src/main/java/app/revanced/extension/music/patches/HideGetPremiumPatch.java @@ -0,0 +1,14 @@ +package app.revanced.extension.music.patches; + +import app.revanced.extension.music.settings.Settings; + +@SuppressWarnings("unused") +public class HideGetPremiumPatch { + + /** + * Injection point + */ + public static boolean hideGetPremiumLabel() { + return Settings.HIDE_GET_PREMIUM_LABEL.get(); + } +} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/HideUpgradeButtonPatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/HideUpgradeButtonPatch.java new file mode 100644 index 000000000..8d3e022b1 --- /dev/null +++ b/extensions/music/src/main/java/app/revanced/extension/music/patches/HideUpgradeButtonPatch.java @@ -0,0 +1,14 @@ +package app.revanced.extension.music.patches; + +import app.revanced.extension.music.settings.Settings; + +@SuppressWarnings("unused") +public class HideUpgradeButtonPatch { + + /** + * Injection point + */ + public static boolean hideUpgradeButton() { + return Settings.HIDE_UPGRADE_BUTTON.get(); + } +} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/HideVideoAdsPatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/HideVideoAdsPatch.java new file mode 100644 index 000000000..9c4d51ee3 --- /dev/null +++ b/extensions/music/src/main/java/app/revanced/extension/music/patches/HideVideoAdsPatch.java @@ -0,0 +1,17 @@ +package app.revanced.extension.music.patches; + +import app.revanced.extension.music.settings.Settings; + +@SuppressWarnings("unused") +public class HideVideoAdsPatch { + + /** + * Injection point + */ + public static boolean showVideoAds(boolean original) { + if (Settings.HIDE_VIDEO_ADS.get()) { + return false; + } + return original; + } +} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/PermanentRepeatPatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/PermanentRepeatPatch.java new file mode 100644 index 000000000..b44b0a3f1 --- /dev/null +++ b/extensions/music/src/main/java/app/revanced/extension/music/patches/PermanentRepeatPatch.java @@ -0,0 +1,14 @@ +package app.revanced.extension.music.patches; + +import app.revanced.extension.music.settings.Settings; + +@SuppressWarnings("unused") +public class PermanentRepeatPatch { + + /** + * Injection point + */ + public static boolean permanentRepeat() { + return Settings.PERMANENT_REPEAT.get(); + } +} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/GoogleApiActivityHook.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/GoogleApiActivityHook.java new file mode 100644 index 000000000..8597113c6 --- /dev/null +++ b/extensions/music/src/main/java/app/revanced/extension/music/settings/GoogleApiActivityHook.java @@ -0,0 +1,84 @@ +package app.revanced.extension.music.settings; + +import android.app.Activity; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.preference.PreferenceFragment; +import android.view.View; + +import app.revanced.extension.music.settings.preference.ReVancedPreferenceFragment; +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.Utils; +import app.revanced.extension.shared.settings.BaseActivityHook; + +/** + * Hooks GoogleApiActivity to inject a custom ReVancedPreferenceFragment with a toolbar. + */ +public class GoogleApiActivityHook extends BaseActivityHook { + /** + * Injection point + *

+ * Creates an instance of GoogleApiActivityHook for use in static initialization. + */ + @SuppressWarnings("unused") + public static GoogleApiActivityHook createInstance() { + // Must touch the Music settings to ensure the class is loaded and + // the values can be found when setting the UI preferences. + // Logging anything under non debug ensures this is set. + Logger.printInfo(() -> "Permanent repeat enabled: " + Settings.PERMANENT_REPEAT.get()); + + return new GoogleApiActivityHook(); + } + + /** + * Sets the fixed theme for the activity. + */ + @Override + protected void customizeActivityTheme(Activity activity) { + // Override the default YouTube Music theme to increase start padding of list items. + // Custom style located in resources/music/values/style.xml + activity.setTheme(Utils.getResourceIdentifier("Theme.ReVanced.YouTubeMusic.Settings", "style")); + } + + /** + * Returns the resource ID for the YouTube Music settings layout. + */ + @Override + protected int getContentViewResourceId() { + return Utils.getResourceIdentifier("revanced_music_settings_with_toolbar", "layout"); + } + + /** + * Returns the fixed background color for the toolbar. + */ + @Override + protected int getToolbarBackgroundColor() { + return Utils.getResourceColor("ytm_color_black"); + } + + /** + * Returns the navigation icon with a color filter applied. + */ + @Override + protected Drawable getNavigationIcon() { + Drawable navigationIcon = ReVancedPreferenceFragment.getBackButtonDrawable(); + navigationIcon.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN); + return navigationIcon; + } + + /** + * Returns the click listener that finishes the activity when the navigation icon is clicked. + */ + @Override + protected View.OnClickListener getNavigationClickListener(Activity activity) { + return view -> activity.finish(); + } + + /** + * Creates a new ReVancedPreferenceFragment for the activity. + */ + @Override + protected PreferenceFragment createPreferenceFragment() { + return new ReVancedPreferenceFragment(); + } +} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java new file mode 100644 index 000000000..394cc7b3e --- /dev/null +++ b/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java @@ -0,0 +1,21 @@ +package app.revanced.extension.music.settings; + +import static java.lang.Boolean.FALSE; +import static java.lang.Boolean.TRUE; + +import app.revanced.extension.shared.settings.BaseSettings; +import app.revanced.extension.shared.settings.BooleanSetting; + +public class Settings extends BaseSettings { + + // Ads + public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_music_hide_video_ads", TRUE, true); + public static final BooleanSetting HIDE_GET_PREMIUM_LABEL = new BooleanSetting("revanced_music_hide_get_premium_label", TRUE, true); + public static final BooleanSetting HIDE_UPGRADE_BUTTON = new BooleanSetting("revanced_music_hide_upgrade_button", TRUE, true); + + // General + public static final BooleanSetting HIDE_CATEGORY_BAR = new BooleanSetting("revanced_music_hide_category_bar", FALSE, true); + + // Player + public static final BooleanSetting PERMANENT_REPEAT = new BooleanSetting("revanced_music_play_permanent_repeat", FALSE, true); +} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/preference/ReVancedPreferenceFragment.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/preference/ReVancedPreferenceFragment.java new file mode 100644 index 000000000..67ca69ba4 --- /dev/null +++ b/extensions/music/src/main/java/app/revanced/extension/music/settings/preference/ReVancedPreferenceFragment.java @@ -0,0 +1,38 @@ +package app.revanced.extension.music.settings.preference; + +import android.widget.Toolbar; + +import app.revanced.extension.music.settings.GoogleApiActivityHook; +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.Utils; +import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment; + +/** + * Preference fragment for ReVanced settings. + */ +@SuppressWarnings({"deprecation", "NewApi"}) +public class ReVancedPreferenceFragment extends ToolbarPreferenceFragment { + + /** + * Initializes the preference fragment. + */ + @Override + protected void initialize() { + super.initialize(); + + try { + Utils.sortPreferenceGroups(getPreferenceScreen()); + setPreferenceScreenToolbar(getPreferenceScreen()); + } catch (Exception ex) { + Logger.printException(() -> "initialize failure", ex); + } + } + + /** + * Sets toolbar for all nested preference screens. + */ + @Override + protected void customizeToolbar(Toolbar toolbar) { + GoogleApiActivityHook.setToolbarLayoutParams(toolbar); + } +} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseActivityHook.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseActivityHook.java new file mode 100644 index 000000000..a24897ca5 --- /dev/null +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseActivityHook.java @@ -0,0 +1,142 @@ +package app.revanced.extension.shared.settings; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.graphics.drawable.Drawable; +import android.preference.PreferenceFragment; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import android.widget.Toolbar; + +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.Utils; +import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment; + +/** + * Base class for hooking activities to inject a custom PreferenceFragment with a toolbar. + * Provides common logic for initializing the activity and setting up the toolbar. + */ +@SuppressWarnings({"deprecation", "NewApi"}) +public abstract class BaseActivityHook extends Activity { + + /** + * Layout parameters for the toolbar, extracted from the dummy toolbar. + */ + protected static ViewGroup.LayoutParams toolbarLayoutParams; + + /** + * Sets the layout parameters for the toolbar. + */ + public static void setToolbarLayoutParams(Toolbar toolbar) { + if (toolbarLayoutParams != null) { + toolbar.setLayoutParams(toolbarLayoutParams); + } + } + + /** + * Initializes the activity by setting the theme, content view and injecting a PreferenceFragment. + */ + public static void initialize(BaseActivityHook hook, Activity activity) { + try { + hook.customizeActivityTheme(activity); + activity.setContentView(hook.getContentViewResourceId()); + + // Sanity check. + String dataString = activity.getIntent().getDataString(); + if (!"revanced_settings_intent".equals(dataString)) { + Logger.printException(() -> "Unknown intent: " + dataString); + return; + } + + PreferenceFragment fragment = hook.createPreferenceFragment(); + hook.createToolbar(activity, fragment); + + activity.getFragmentManager() + .beginTransaction() + .replace(Utils.getResourceIdentifier("revanced_settings_fragments", "id"), fragment) + .commit(); + } catch (Exception ex) { + Logger.printException(() -> "initialize failure", ex); + } + } + + /** + * Creates and configures a toolbar for the activity, replacing a dummy placeholder. + */ + @SuppressLint("UseCompatLoadingForDrawables") + protected void createToolbar(Activity activity, PreferenceFragment fragment) { + // Replace dummy placeholder toolbar. + // This is required to fix submenu title alignment issue with Android ASOP 15+ + ViewGroup toolBarParent = activity.findViewById( + Utils.getResourceIdentifier("revanced_toolbar_parent", "id")); + ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolBarParent, "revanced_toolbar"); + toolbarLayoutParams = dummyToolbar.getLayoutParams(); + toolBarParent.removeView(dummyToolbar); + + // Sets appropriate system navigation bar color for the activity. + ToolbarPreferenceFragment.setNavigationBarColor(activity.getWindow()); + + Toolbar toolbar = new Toolbar(toolBarParent.getContext()); + toolbar.setBackgroundColor(getToolbarBackgroundColor()); + toolbar.setNavigationIcon(getNavigationIcon()); + toolbar.setNavigationOnClickListener(getNavigationClickListener(activity)); + toolbar.setTitle(Utils.getResourceIdentifier("revanced_settings_title", "string")); + + final int margin = Utils.dipToPixels(16); + toolbar.setTitleMarginStart(margin); + toolbar.setTitleMarginEnd(margin); + TextView toolbarTextView = Utils.getChildView(toolbar, false, view -> view instanceof TextView); + if (toolbarTextView != null) { + toolbarTextView.setTextColor(Utils.getAppForegroundColor()); + toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20); + } + setToolbarLayoutParams(toolbar); + + onPostToolbarSetup(activity, toolbar, fragment); + + toolBarParent.addView(toolbar, 0); + } + + /** + * Customizes the activity's theme. + */ + protected abstract void customizeActivityTheme(Activity activity); + + /** + * Returns the resource ID for the content view layout. + */ + protected abstract int getContentViewResourceId(); + + /** + * Returns the background color for the toolbar. + */ + protected abstract int getToolbarBackgroundColor(); + + /** + * Returns the navigation icon drawable for the toolbar. + */ + protected abstract Drawable getNavigationIcon(); + + /** + * Returns the click listener for the toolbar's navigation icon. + */ + protected abstract View.OnClickListener getNavigationClickListener(Activity activity); + + /** + * Creates the PreferenceFragment to be injected into the activity. + */ + protected PreferenceFragment createPreferenceFragment() { + return new ToolbarPreferenceFragment(); + } + + /** + * Performs additional setup after the toolbar is configured. + * + * @param activity The activity hosting the toolbar. + * @param toolbar The configured toolbar. + * @param fragment The PreferenceFragment associated with the activity. + */ + protected void onPostToolbarSetup(Activity activity, Toolbar toolbar, PreferenceFragment fragment) {} +} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ClearLogBufferPreference.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ClearLogBufferPreference.java similarity index 88% rename from extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ClearLogBufferPreference.java rename to extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ClearLogBufferPreference.java index 109c6c8e7..7dbf0dd38 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ClearLogBufferPreference.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ClearLogBufferPreference.java @@ -1,9 +1,8 @@ -package app.revanced.extension.youtube.settings.preference; +package app.revanced.extension.shared.settings.preference; import android.content.Context; import android.util.AttributeSet; import android.preference.Preference; -import app.revanced.extension.shared.settings.preference.LogBufferManager; /** * A custom preference that clears the ReVanced debug log buffer when clicked. diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ExportLogToClipboardPreference.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ExportLogToClipboardPreference.java similarity index 88% rename from extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ExportLogToClipboardPreference.java rename to extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ExportLogToClipboardPreference.java index fac1cfa79..57fb12823 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ExportLogToClipboardPreference.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ExportLogToClipboardPreference.java @@ -1,9 +1,8 @@ -package app.revanced.extension.youtube.settings.preference; +package app.revanced.extension.shared.settings.preference; import android.content.Context; import android.util.AttributeSet; import android.preference.Preference; -import app.revanced.extension.shared.settings.preference.LogBufferManager; /** * A custom preference that triggers exporting ReVanced debug logs to the clipboard when clicked. diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ToolbarPreferenceFragment.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ToolbarPreferenceFragment.java new file mode 100644 index 000000000..05a1fddcc --- /dev/null +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ToolbarPreferenceFragment.java @@ -0,0 +1,150 @@ +package app.revanced.extension.shared.settings.preference; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.graphics.Insets; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.preference.Preference; +import android.preference.PreferenceScreen; +import android.util.TypedValue; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowInsets; +import android.widget.TextView; +import android.widget.Toolbar; + +import androidx.annotation.Nullable; + +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.Utils; +import app.revanced.extension.shared.settings.BaseActivityHook; + +@SuppressWarnings({"deprecation", "NewApi"}) +public class ToolbarPreferenceFragment extends AbstractPreferenceFragment { + /** + * Sets toolbar for all nested preference screens. + */ + protected void setPreferenceScreenToolbar(PreferenceScreen parentScreen) { + for (int i = 0, count = parentScreen.getPreferenceCount(); i < count; i++) { + Preference childPreference = parentScreen.getPreference(i); + if (childPreference instanceof PreferenceScreen) { + // Recursively set sub preferences. + setPreferenceScreenToolbar((PreferenceScreen) childPreference); + + childPreference.setOnPreferenceClickListener( + childScreen -> { + Dialog preferenceScreenDialog = ((PreferenceScreen) childScreen).getDialog(); + ViewGroup rootView = (ViewGroup) preferenceScreenDialog + .findViewById(android.R.id.content) + .getParent(); + + // Allow package-specific background customization. + customizeDialogBackground(rootView); + + // Fix the system navigation bar color for submenus. + setNavigationBarColor(preferenceScreenDialog.getWindow()); + + // Fix edge-to-edge screen with Android 15 and YT 19.45+ + // https://developer.android.com/develop/ui/views/layout/edge-to-edge#system-bars-insets + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + rootView.setOnApplyWindowInsetsListener((v, insets) -> { + Insets statusInsets = insets.getInsets(WindowInsets.Type.statusBars()); + Insets navInsets = insets.getInsets(WindowInsets.Type.navigationBars()); + Insets cutoutInsets = insets.getInsets(WindowInsets.Type.displayCutout()); + + // Apply padding for display cutout in landscape. + int leftPadding = cutoutInsets.left; + int rightPadding = cutoutInsets.right; + int topPadding = statusInsets.top; + int bottomPadding = navInsets.bottom; + + v.setPadding(leftPadding, topPadding, rightPadding, bottomPadding); + return insets; + }); + } + + Toolbar toolbar = new Toolbar(childScreen.getContext()); + toolbar.setTitle(childScreen.getTitle()); + toolbar.setNavigationIcon(getBackButtonDrawable()); + toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss()); + + final int margin = Utils.dipToPixels(16); + toolbar.setTitleMargin(margin, 0, margin, 0); + + TextView toolbarTextView = Utils.getChildView(toolbar, + true, TextView.class::isInstance); + if (toolbarTextView != null) { + toolbarTextView.setTextColor(Utils.getAppForegroundColor()); + toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20); + } + + // Allow package-specific toolbar customization. + customizeToolbar(toolbar); + + // Allow package-specific post-toolbar setup. + onPostToolbarSetup(toolbar, preferenceScreenDialog); + + rootView.addView(toolbar, 0); + return false; + } + ); + } + } + } + + /** + * Sets the system navigation bar color for the activity. + * Applies the background color obtained from {@link Utils#getAppBackgroundColor()} to the navigation bar. + * For Android 10 (API 29) and above, enforces navigation bar contrast to ensure visibility. + */ + public static void setNavigationBarColor(@Nullable Window window) { + if (window == null) { + Logger.printDebug(() -> "Cannot set navigation bar color, window is null"); + return; + } + + window.setNavigationBarColor(Utils.getAppBackgroundColor()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + window.setNavigationBarContrastEnforced(true); + } + } + + /** + * Returns the drawable for the back button. + */ + @SuppressLint("UseCompatLoadingForDrawables") + public static Drawable getBackButtonDrawable() { + final int backButtonResource = Utils.getResourceIdentifier( + "revanced_settings_toolbar_arrow_left", "drawable"); + Drawable drawable = Utils.getContext().getResources().getDrawable(backButtonResource); + customizeBackButtonDrawable(drawable); + return drawable; + } + + /** + * Customizes the back button drawable. + */ + protected static void customizeBackButtonDrawable(Drawable drawable) { + drawable.setTint(Utils.getAppForegroundColor()); + } + + /** + * Allows subclasses to customize the dialog's root view background. + */ + protected void customizeDialogBackground(ViewGroup rootView) { + rootView.setBackgroundColor(Utils.getAppBackgroundColor()); + } + + /** + * Allows subclasses to customize the toolbar. + */ + protected void customizeToolbar(Toolbar toolbar) { + BaseActivityHook.setToolbarLayoutParams(toolbar); + } + + /** + * Allows subclasses to perform actions after toolbar setup. + */ + protected void onPostToolbarSetup(Toolbar toolbar, Dialog preferenceScreenDialog) {} +} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/LicenseActivityHook.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/LicenseActivityHook.java index 24d3a4f42..5c4d3ca77 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/LicenseActivityHook.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/LicenseActivityHook.java @@ -1,50 +1,120 @@ package app.revanced.extension.youtube.settings; -import static app.revanced.extension.shared.Utils.getResourceIdentifier; - import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.res.Configuration; +import android.graphics.drawable.Drawable; import android.preference.PreferenceFragment; -import android.util.TypedValue; -import android.view.ViewGroup; -import android.widget.TextView; +import android.view.View; import android.widget.Toolbar; -import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.settings.AppLanguage; +import app.revanced.extension.shared.settings.BaseActivityHook; import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.youtube.patches.VersionCheckPatch; import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch; import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment; /** - * Hooks LicenseActivity. - *

- * This class is responsible for injecting our own fragment by replacing the LicenseActivity. + * Hooks LicenseActivity to inject a custom ReVancedPreferenceFragment with a toolbar and search functionality. */ -@SuppressWarnings("unused") -public class LicenseActivityHook extends Activity { +@SuppressWarnings("deprecation") +public class LicenseActivityHook extends BaseActivityHook { private static int currentThemeValueOrdinal = -1; // Must initially be a non-valid enum ordinal value. - private static ViewGroup.LayoutParams toolbarLayoutParams; - + /** + * Controller for managing search view components in the toolbar. + */ @SuppressLint("StaticFieldLeak") public static SearchViewController searchViewController; - public static void setToolbarLayoutParams(Toolbar toolbar) { - if (toolbarLayoutParams != null) { - toolbar.setLayoutParams(toolbarLayoutParams); + /** + * Injection point + *

+ * Creates an instance of LicenseActivityHook for use in static initialization. + */ + @SuppressWarnings("unused") + public static LicenseActivityHook createInstance() { + return new LicenseActivityHook(); + } + + /** + * Customizes the activity theme based on dark/light mode. + */ + @Override + protected void customizeActivityTheme(Activity activity) { + final var theme = Utils.isDarkModeEnabled() + ? "Theme.YouTube.Settings.Dark" + : "Theme.YouTube.Settings"; + activity.setTheme(Utils.getResourceIdentifier(theme, "style")); + } + + /** + * Returns the resource ID for the YouTube settings layout. + */ + @Override + protected int getContentViewResourceId() { + return Utils.getResourceIdentifier("revanced_settings_with_toolbar", "layout"); + } + + /** + * Returns the toolbar background color based on dark/light mode. + */ + @Override + protected int getToolbarBackgroundColor() { + final String colorName = Utils.isDarkModeEnabled() + ? "yt_black3" + : "yt_white1"; + return Utils.getColorFromString(colorName); + } + + /** + * Returns the navigation icon drawable for the toolbar. + */ + @Override + protected Drawable getNavigationIcon() { + return ReVancedPreferenceFragment.getBackButtonDrawable(); + } + + /** + * Returns the click listener for the navigation icon. + */ + @Override + protected View.OnClickListener getNavigationClickListener(Activity activity) { + return null; + } + + /** + * Adds search view components to the toolbar for ReVancedPreferenceFragment. + * + * @param activity The activity hosting the toolbar. + * @param toolbar The configured toolbar. + * @param fragment The PreferenceFragment associated with the activity. + */ + @Override + protected void onPostToolbarSetup(Activity activity, Toolbar toolbar, PreferenceFragment fragment) { + if (fragment instanceof ReVancedPreferenceFragment) { + searchViewController = SearchViewController.addSearchViewComponents( + activity, toolbar, (ReVancedPreferenceFragment) fragment); } } + /** + * Creates a new ReVancedPreferenceFragment for the activity. + */ + @Override + protected PreferenceFragment createPreferenceFragment() { + return new ReVancedPreferenceFragment(); + } + /** * Injection point. * Overrides the ReVanced settings language. */ + @SuppressWarnings("unused") public static Context getAttachBaseContext(Context original) { AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get(); if (language == AppLanguage.DEFAULT) { @@ -57,6 +127,7 @@ public class LicenseActivityHook extends Activity { /** * Injection point. */ + @SuppressWarnings("unused") public static boolean useCairoSettingsFragment(boolean original) { // Early targets have layout issues and it's better to always force off. if (!VersionCheckPatch.IS_19_34_OR_GREATER) { @@ -80,87 +151,6 @@ public class LicenseActivityHook extends Activity { /** * Injection point. *

- * Hooks LicenseActivity#onCreate in order to inject our own fragment. - */ - public static void initialize(Activity licenseActivity) { - try { - setActivityTheme(licenseActivity); - ReVancedPreferenceFragment.setNavigationBarColor(licenseActivity.getWindow()); - licenseActivity.setContentView(getResourceIdentifier( - "revanced_settings_with_toolbar", "layout")); - - // Sanity check. - String dataString = licenseActivity.getIntent().getDataString(); - if (!"revanced_settings_intent".equals(dataString)) { - Logger.printException(() -> "Unknown intent: " + dataString); - return; - } - - PreferenceFragment fragment = new ReVancedPreferenceFragment(); - createToolbar(licenseActivity, fragment); - - //noinspection deprecation - licenseActivity.getFragmentManager() - .beginTransaction() - .replace(getResourceIdentifier("revanced_settings_fragments", "id"), fragment) - .commit(); - } catch (Exception ex) { - Logger.printException(() -> "initialize failure", ex); - } - } - - @SuppressLint("UseCompatLoadingForDrawables") - private static void createToolbar(Activity activity, PreferenceFragment fragment) { - // Replace dummy placeholder toolbar. - // This is required to fix submenu title alignment issue with Android ASOP 15+ - ViewGroup toolBarParent = activity.findViewById( - getResourceIdentifier("revanced_toolbar_parent", "id")); - ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolBarParent, "revanced_toolbar"); - toolbarLayoutParams = dummyToolbar.getLayoutParams(); - toolBarParent.removeView(dummyToolbar); - - Toolbar toolbar = new Toolbar(toolBarParent.getContext()); - toolbar.setBackgroundColor(getToolbarBackgroundColor()); - toolbar.setNavigationIcon(ReVancedPreferenceFragment.getBackButtonDrawable()); - toolbar.setTitle(getResourceIdentifier("revanced_settings_title", "string")); - - final int margin = Utils.dipToPixels(16); - toolbar.setTitleMarginStart(margin); - toolbar.setTitleMarginEnd(margin); - TextView toolbarTextView = Utils.getChildView(toolbar, false, - view -> view instanceof TextView); - if (toolbarTextView != null) { - toolbarTextView.setTextColor(Utils.getAppForegroundColor()); - toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20); - } - setToolbarLayoutParams(toolbar); - - // Add Search bar only for ReVancedPreferenceFragment. - if (fragment instanceof ReVancedPreferenceFragment) { - searchViewController = SearchViewController.addSearchViewComponents(activity, toolbar, (ReVancedPreferenceFragment) fragment); - } - - toolBarParent.addView(toolbar, 0); - } - - public static void setActivityTheme(Activity activity) { - final var theme = Utils.isDarkModeEnabled() - ? "Theme.YouTube.Settings.Dark" - : "Theme.YouTube.Settings"; - activity.setTheme(getResourceIdentifier(theme, "style")); - } - - public static int getToolbarBackgroundColor() { - final String colorName = Utils.isDarkModeEnabled() - ? "yt_black3" - : "yt_white1"; - - return Utils.getColorFromString(colorName); - } - - /** - * Injection point. - * * Updates dark/light mode since YT settings can force light/dark mode * which can differ from the global device settings. */ @@ -173,6 +163,10 @@ public class LicenseActivityHook extends Activity { } } + /** + * Handles configuration changes, such as orientation, to update the search view. + */ + @SuppressWarnings("unused") public static void handleConfigurationChanged(Activity activity, Configuration newConfig) { if (searchViewController != null) { searchViewController.handleOrientationChange(newConfig.orientation); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java index c4cb42313..c96ed26ed 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java @@ -3,11 +3,7 @@ package app.revanced.extension.youtube.settings.preference; import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.Utils.getResourceIdentifier; -import android.annotation.SuppressLint; import android.app.Dialog; -import android.graphics.Insets; -import android.graphics.drawable.Drawable; -import android.os.Build; import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceCategory; @@ -17,11 +13,6 @@ import android.preference.SwitchPreference; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.style.BackgroundColorSpan; -import android.util.TypedValue; -import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowInsets; -import android.widget.TextView; import android.widget.Toolbar; import androidx.annotation.CallSuper; @@ -40,16 +31,16 @@ import java.util.regex.Pattern; import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.settings.BaseSettings; -import app.revanced.extension.shared.settings.preference.AbstractPreferenceFragment; import app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory; +import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment; import app.revanced.extension.youtube.settings.LicenseActivityHook; import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGroup; /** * Preference fragment for ReVanced settings. */ -@SuppressWarnings("deprecation") -public class ReVancedPreferenceFragment extends AbstractPreferenceFragment { +@SuppressWarnings({"deprecation", "NewApi"}) +public class ReVancedPreferenceFragment extends ToolbarPreferenceFragment { /** * The main PreferenceScreen used to display the current set of preferences. @@ -70,31 +61,6 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment { */ private final List> allPreferences = new ArrayList<>(); - @SuppressLint("UseCompatLoadingForDrawables") - public static Drawable getBackButtonDrawable() { - final int backButtonResource = getResourceIdentifier("revanced_settings_toolbar_arrow_left", "drawable"); - Drawable drawable = Utils.getContext().getResources().getDrawable(backButtonResource); - drawable.setTint(Utils.getAppForegroundColor()); - return drawable; - } - - /** - * Sets the system navigation bar color for the activity. - * Applies the background color obtained from {@link Utils#getAppBackgroundColor()} to the navigation bar. - * For Android 10 (API 29) and above, enforces navigation bar contrast to ensure visibility. - */ - public static void setNavigationBarColor(@Nullable Window window) { - if (window == null) { - Logger.printDebug(() -> "Cannot set navigation bar color, window is null"); - return; - } - - window.setNavigationBarColor(Utils.getAppBackgroundColor()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - window.setNavigationBarContrastEnforced(true); - } - } - /** * Initializes the preference fragment, copying the original screen to allow full restoration. */ @@ -139,8 +105,28 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment { } } + /** + * Sets toolbar for all nested preference screens. + */ + @Override + protected void customizeToolbar(Toolbar toolbar) { + LicenseActivityHook.setToolbarLayoutParams(toolbar); + } + + /** + * Perform actions after toolbar setup. + */ + @Override + protected void onPostToolbarSetup(Toolbar toolbar, Dialog preferenceScreenDialog) { + if (LicenseActivityHook.searchViewController != null + && LicenseActivityHook.searchViewController.isSearchActive()) { + toolbar.post(() -> LicenseActivityHook.searchViewController.closeSearch()); + } + } + /** * Recursively collects all preferences from the screen or group. + * * @param includeDepth Menu depth to start including preferences. * A value of 0 adds all preferences. */ @@ -222,75 +208,6 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment { preferenceScreen.addPreference(noResultsPreference); } } - - /** - * Sets toolbar for all nested preference screens. - */ - private void setPreferenceScreenToolbar(PreferenceScreen parentScreen) { - for (int i = 0, count = parentScreen.getPreferenceCount(); i < count; i++) { - Preference childPreference = parentScreen.getPreference(i); - if (childPreference instanceof PreferenceScreen) { - // Recursively set sub preferences. - setPreferenceScreenToolbar((PreferenceScreen) childPreference); - - childPreference.setOnPreferenceClickListener( - childScreen -> { - Dialog preferenceScreenDialog = ((PreferenceScreen) childScreen).getDialog(); - ViewGroup rootView = (ViewGroup) preferenceScreenDialog - .findViewById(android.R.id.content) - .getParent(); - - // Fix the system navigation bar color for submenus. - setNavigationBarColor(preferenceScreenDialog.getWindow()); - - // Fix edge-to-edge screen with Android 15 and YT 19.45+ - // https://developer.android.com/develop/ui/views/layout/edge-to-edge#system-bars-insets - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - rootView.setOnApplyWindowInsetsListener((v, insets) -> { - Insets statusInsets = insets.getInsets(WindowInsets.Type.statusBars()); - Insets navInsets = insets.getInsets(WindowInsets.Type.navigationBars()); - Insets cutoutInsets = insets.getInsets(WindowInsets.Type.displayCutout()); - - // Apply padding for display cutout in landscape. - int leftPadding = cutoutInsets.left; - int rightPadding = cutoutInsets.right; - int topPadding = statusInsets.top; - int bottomPadding = navInsets.bottom; - - v.setPadding(leftPadding, topPadding, rightPadding, bottomPadding); - return insets; - }); - } - - Toolbar toolbar = new Toolbar(childScreen.getContext()); - toolbar.setTitle(childScreen.getTitle()); - toolbar.setNavigationIcon(getBackButtonDrawable()); - toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss()); - - final int margin = Utils.dipToPixels(16); - toolbar.setTitleMargin(margin, 0, margin, 0); - - TextView toolbarTextView = Utils.getChildView(toolbar, - true, TextView.class::isInstance); - if (toolbarTextView != null) { - toolbarTextView.setTextColor(Utils.getAppForegroundColor()); - toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20); - } - - LicenseActivityHook.setToolbarLayoutParams(toolbar); - - if (LicenseActivityHook.searchViewController != null - && LicenseActivityHook.searchViewController.isSearchActive()) { - toolbar.post(() -> LicenseActivityHook.searchViewController.closeSearch()); - } - - rootView.addView(toolbar, 0); - return false; - } - ); - } - } - } } @SuppressWarnings("deprecation") diff --git a/patches/api/patches.api b/patches/api/patches.api index 70df3fdcc..00b58e88e 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -372,8 +372,9 @@ public final class app/revanced/patches/music/layout/premium/HideGetPremiumPatch public static final fun getHideGetPremiumPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } -public final class app/revanced/patches/music/layout/upgradebutton/RemoveUpgradeButtonPatchKt { - public static final fun getRemoveUpgradeButtonPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +public final class app/revanced/patches/music/layout/upgradebutton/HideUpgradeButtonPatchKt { + public static final fun getHideUpgradeButton ()Lapp/revanced/patcher/patch/BytecodePatch; + public static final fun getRemoveUpgradeButton ()Lapp/revanced/patcher/patch/BytecodePatch; } public final class app/revanced/patches/music/misc/androidauto/BypassCertificateChecksPatchKt { @@ -396,7 +397,21 @@ public final class app/revanced/patches/music/misc/gms/GmsCoreSupportPatchKt { public static final fun getGmsCoreSupportPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } -public final class app/revanced/patches/music/misc/spoof/SpoofVideoStreamsKt { +public final class app/revanced/patches/music/misc/settings/PreferenceScreen : app/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen { + public static final field INSTANCE Lapp/revanced/patches/music/misc/settings/PreferenceScreen; + public fun commit (Lapp/revanced/patches/shared/misc/settings/preference/PreferenceScreenPreference;)V + public final fun getADS ()Lapp/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen$Screen; + public final fun getGENERAL ()Lapp/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen$Screen; + public final fun getMISC ()Lapp/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen$Screen; + public final fun getPLAYER ()Lapp/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen$Screen; +} + +public final class app/revanced/patches/music/misc/settings/SettingsPatchKt { + public static final fun getSettingsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; + public static final fun newIntent (Ljava/lang/String;)Lapp/revanced/patches/shared/misc/settings/preference/IntentPreference$Intent; +} + +public final class app/revanced/patches/music/misc/spoof/SpoofVideoStreamsPatchKt { public static final fun getSpoofVideoStreamsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } @@ -1652,7 +1667,6 @@ public final class app/revanced/patches/youtube/misc/settings/PreferenceScreen : } public final class app/revanced/patches/youtube/misc/settings/SettingsPatchKt { - public static final fun addSettingPreference (Lapp/revanced/patches/shared/misc/settings/preference/BasePreference;)V public static final fun getSettingsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun newIntent (Ljava/lang/String;)Lapp/revanced/patches/shared/misc/settings/preference/IntentPreference$Intent; } diff --git a/patches/src/main/kotlin/app/revanced/patches/music/ad/video/HideVideoAds.kt b/patches/src/main/kotlin/app/revanced/patches/music/ad/video/HideVideoAds.kt index 4c89bfb03..fd417bb5d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/ad/video/HideVideoAds.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/ad/video/HideVideoAds.kt @@ -1,13 +1,27 @@ package app.revanced.patches.music.ad.video -import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.music.misc.extension.sharedExtensionPatch +import app.revanced.patches.music.misc.settings.PreferenceScreen +import app.revanced.patches.music.misc.settings.settingsPatch +import app.revanced.patches.shared.misc.settings.preference.SwitchPreference + +private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/HideVideoAdsPatch;" @Suppress("unused") val hideVideoAdsPatch = bytecodePatch( name = "Hide music video ads", - description = "Hides ads that appear while listening to or streaming music videos, podcasts, or songs.", + description = "Adds an option to hide ads that appear while listening to or streaming music videos, podcasts, or songs.", ) { + dependsOn( + sharedExtensionPatch, + settingsPatch, + addResourcesPatch, + ) + compatibleWith( "com.google.android.apps.youtube.music"( "7.29.52" @@ -15,9 +29,21 @@ val hideVideoAdsPatch = bytecodePatch( ) execute { + addResources("music", "ad.video.hideVideoAdsPatch") + + PreferenceScreen.ADS.addPreferences( + SwitchPreference("revanced_music_hide_video_ads"), + ) + navigate(showVideoAdsParentFingerprint.originalMethod) .to(showVideoAdsParentFingerprint.patternMatch!!.startIndex + 1) .stop() - .addInstruction(0, "const/4 p1, 0x0") + .addInstructions( + 0, + """ + invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->showVideoAds(Z)Z + move-result p1 + """ + ) } } diff --git a/patches/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/EnableExclusiveAudioPlayback.kt b/patches/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/EnableExclusiveAudioPlayback.kt index 9834f649e..4d598e402 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/EnableExclusiveAudioPlayback.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/audio/exclusiveaudio/EnableExclusiveAudioPlayback.kt @@ -1,6 +1,8 @@ package app.revanced.patches.music.audio.exclusiveaudio import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.music.misc.extension.sharedExtensionPatch +import app.revanced.patches.music.misc.settings.settingsPatch import app.revanced.util.returnEarly @Suppress("unused") @@ -8,6 +10,11 @@ val enableExclusiveAudioPlaybackPatch = bytecodePatch( name = "Enable exclusive audio playback", description = "Enables the option to play audio without video.", ) { + dependsOn( + sharedExtensionPatch, + settingsPatch, + ) + compatibleWith( "com.google.android.apps.youtube.music"( "7.29.52" diff --git a/patches/src/main/kotlin/app/revanced/patches/music/interaction/permanentrepeat/PermanentRepeatPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/interaction/permanentrepeat/PermanentRepeatPatch.kt index bfcdaba38..559bacc39 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/interaction/permanentrepeat/PermanentRepeatPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/interaction/permanentrepeat/PermanentRepeatPatch.kt @@ -4,13 +4,27 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith import app.revanced.patcher.extensions.InstructionExtensions.instructions import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.util.smali.ExternalLabel +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.music.misc.extension.sharedExtensionPatch +import app.revanced.patches.music.misc.settings.PreferenceScreen +import app.revanced.patches.music.misc.settings.settingsPatch +import app.revanced.patches.shared.misc.settings.preference.SwitchPreference +import app.revanced.util.findFreeRegister + +private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/PermanentRepeatPatch;" @Suppress("unused") val permanentRepeatPatch = bytecodePatch( name = "Permanent repeat", - description = "Permanently remember your repeating preference even if the playlist ends or another track is played.", - use = false, + description = "Adds an option to always repeat even if the playlist ends or another track is played." ) { + dependsOn( + sharedExtensionPatch, + settingsPatch, + addResourcesPatch, + ) + compatibleWith( "com.google.android.apps.youtube.music"( "7.29.52" @@ -18,13 +32,27 @@ val permanentRepeatPatch = bytecodePatch( ) execute { + addResources("music", "interaction.permanentrepeat.permanentRepeatPatch") + + PreferenceScreen.PLAYER.addPreferences( + SwitchPreference("revanced_music_play_permanent_repeat"), + ) + val startIndex = repeatTrackFingerprint.patternMatch!!.endIndex val repeatIndex = startIndex + 1 repeatTrackFingerprint.method.apply { + // Start index is at a branch, but the same + // register is clobbered in both branch paths. + val freeRegister = findFreeRegister(startIndex + 1) + addInstructionsWithLabels( startIndex, - "goto :repeat", + """ + invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->permanentRepeat()Z + move-result v$freeRegister + if-nez v$freeRegister, :repeat + """, ExternalLabel("repeat", instructions[repeatIndex]), ) } diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/compactheader/HideCategoryBar.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/compactheader/HideCategoryBar.kt index 78ef86f50..883be02a6 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/compactheader/HideCategoryBar.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/compactheader/HideCategoryBar.kt @@ -1,17 +1,32 @@ package app.revanced.patches.music.layout.compactheader import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.music.misc.extension.sharedExtensionPatch +import app.revanced.patches.music.misc.settings.PreferenceScreen +import app.revanced.patches.music.misc.settings.settingsPatch +import app.revanced.patches.shared.misc.settings.preference.SwitchPreference +import app.revanced.util.addInstructionsAtControlFlowLabel import app.revanced.util.findFreeRegister import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/HideCategoryBarPatch;" + @Suppress("unused") val hideCategoryBar = bytecodePatch( name = "Hide category bar", - description = "Hides the category bar at the top of the homepage.", - use = false, + description = "Adds an option to hide the category bar at the top of the homepage." ) { + dependsOn( + sharedExtensionPatch, + settingsPatch, + addResourcesPatch, + ) + compatibleWith( "com.google.android.apps.youtube.music"( "7.29.52" @@ -19,16 +34,27 @@ val hideCategoryBar = bytecodePatch( ) execute { + addResources("music", "layout.compactheader.hideCategoryBar") + + PreferenceScreen.GENERAL.addPreferences( + SwitchPreference("revanced_music_hide_category_bar"), + ) + constructCategoryBarFingerprint.method.apply { val insertIndex = constructCategoryBarFingerprint.patternMatch!!.startIndex val register = getInstruction(insertIndex - 1).registerA val freeRegister = findFreeRegister(insertIndex, register) - addInstructions( + addInstructionsWithLabels( insertIndex, """ + invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->hideCategoryBar()Z + move-result v$freeRegister + if-eqz v$freeRegister, :show const/16 v$freeRegister, 0x8 invoke-virtual { v$register, v$freeRegister }, Landroid/view/View;->setVisibility(I)V + :show + nop """ ) } diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/premium/HideGetPremiumPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/premium/HideGetPremiumPatch.kt index 7ba3260ef..beea72673 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/premium/HideGetPremiumPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/premium/HideGetPremiumPatch.kt @@ -1,16 +1,31 @@ package app.revanced.patches.music.layout.premium import app.revanced.patcher.extensions.InstructionExtensions.addInstruction -import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.music.misc.extension.sharedExtensionPatch +import app.revanced.patches.music.misc.settings.PreferenceScreen +import app.revanced.patches.music.misc.settings.settingsPatch +import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction +private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/HideGetPremiumPatch;" + +@Suppress("unused") val hideGetPremiumPatch = bytecodePatch( - name = "Hide 'Get Music Premium' label", - description = "Hides the \"Get Music Premium\" label from the account menu and settings.", + name = "Hide 'Get Music Premium'", + description = "Adds an option to hide the \"Get Music Premium\" label in the settings and account menu.", ) { + dependsOn( + sharedExtensionPatch, + settingsPatch, + addResourcesPatch, + ) + compatibleWith( "com.google.android.apps.youtube.music"( "7.29.52" @@ -18,6 +33,12 @@ val hideGetPremiumPatch = bytecodePatch( ) execute { + addResources("music", "layout.premium.hideGetPremiumPatch") + + PreferenceScreen.ADS.addPreferences( + SwitchPreference("revanced_music_hide_get_premium_label"), + ) + hideGetPremiumFingerprint.method.apply { val insertIndex = hideGetPremiumFingerprint.patternMatch!!.endIndex @@ -37,12 +58,17 @@ val hideGetPremiumPatch = bytecodePatch( ) } - membershipSettingsFingerprint.method.addInstructions( + membershipSettingsFingerprint.method.addInstructionsWithLabels( 0, """ - const/4 v0, 0x0 - return-object v0 - """, + invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->hideGetPremiumLabel()Z + move-result v0 + if-eqz v0, :show + const/4 v0, 0x0 + return-object v0 + :show + nop + """ ) } } diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/RemoveUpgradeButtonPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/HideUpgradeButtonPatch.kt similarity index 70% rename from patches/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/RemoveUpgradeButtonPatch.kt rename to patches/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/HideUpgradeButtonPatch.kt index 426ab8046..b54e4f2b6 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/RemoveUpgradeButtonPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/HideUpgradeButtonPatch.kt @@ -7,17 +7,31 @@ import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.extensions.newLabel import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.util.smali.toInstructions +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.music.misc.extension.sharedExtensionPatch +import app.revanced.patches.music.misc.settings.PreferenceScreen +import app.revanced.patches.music.misc.settings.settingsPatch +import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.util.getReference import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction22t import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.reference.FieldReference +private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/HideUpgradeButtonPatch;" + @Suppress("unused") -val removeUpgradeButtonPatch = bytecodePatch( - name = "Remove upgrade button", - description = "Removes the upgrade tab from the pivot bar.", +val hideUpgradeButton = bytecodePatch( + name = "Hide upgrade button", + description = "Hides the upgrade tab from the pivot bar.", ) { + dependsOn( + sharedExtensionPatch, + settingsPatch, + addResourcesPatch, + ) + compatibleWith( "com.google.android.apps.youtube.music"( "7.29.52" @@ -25,6 +39,15 @@ val removeUpgradeButtonPatch = bytecodePatch( ) execute { + addResources("music", "layout.upgradebutton.hideUpgradeButtonPatch") + + // TODO: Add an extension patch to allow this to be enabled/disabled in app. + if (false) { + PreferenceScreen.ADS.addPreferences( + SwitchPreference("revanced_music_hide_upgrade_button") + ) + } + pivotBarConstructorFingerprint.method.apply { val pivotBarElementFieldReference = getInstruction(pivotBarConstructorFingerprint.patternMatch!!.endIndex - 1) @@ -77,3 +100,9 @@ val removeUpgradeButtonPatch = bytecodePatch( } } } + +@Deprecated("Patch was renamed", ReplaceWith("hideUpgradeButton")) +@Suppress("unused") +val removeUpgradeButton = bytecodePatch{ + dependsOn(hideUpgradeButton) +} diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/androidauto/BypassCertificateChecksPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/androidauto/BypassCertificateChecksPatch.kt index a08e1fceb..70e707fbb 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/androidauto/BypassCertificateChecksPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/androidauto/BypassCertificateChecksPatch.kt @@ -1,6 +1,8 @@ package app.revanced.patches.music.misc.androidauto import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.music.misc.extension.sharedExtensionPatch +import app.revanced.patches.music.misc.settings.settingsPatch import app.revanced.util.returnEarly @Suppress("unused") @@ -8,6 +10,11 @@ val bypassCertificateChecksPatch = bytecodePatch( name = "Bypass certificate checks", description = "Bypasses certificate checks which prevent YouTube Music from working on Android Auto.", ) { + dependsOn( + sharedExtensionPatch, + settingsPatch + ) + compatibleWith( "com.google.android.apps.youtube.music"( "7.29.52" diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatch.kt index ab0fe132d..3d81296a7 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatch.kt @@ -1,13 +1,20 @@ package app.revanced.patches.music.misc.backgroundplayback import app.revanced.patcher.extensions.InstructionExtensions.addInstruction -import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.music.misc.extension.sharedExtensionPatch +import app.revanced.patches.music.misc.settings.settingsPatch +import app.revanced.util.returnEarly val backgroundPlaybackPatch = bytecodePatch( name = "Remove background playback restrictions", description = "Removes restrictions on background playback, including playing kids videos in the background.", ) { + dependsOn( + sharedExtensionPatch, + settingsPatch + ) + compatibleWith( "com.google.android.apps.youtube.music"( "7.29.52" @@ -20,12 +27,6 @@ val backgroundPlaybackPatch = bytecodePatch( "return-void", ) - backgroundPlaybackDisableFingerprint.method.addInstructions( - 0, - """ - const/4 v0, 0x1 - return v0 - """, - ) + backgroundPlaybackDisableFingerprint.method.returnEarly(true) } } diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/gms/GmsCoreSupportPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/gms/GmsCoreSupportPatch.kt index 0fa223b23..b61258496 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/gms/GmsCoreSupportPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/gms/GmsCoreSupportPatch.kt @@ -1,12 +1,17 @@ package app.revanced.patches.music.misc.gms import app.revanced.patcher.patch.Option +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.music.misc.extension.sharedExtensionPatch import app.revanced.patches.music.misc.gms.Constants.MUSIC_PACKAGE_NAME import app.revanced.patches.music.misc.gms.Constants.REVANCED_MUSIC_PACKAGE_NAME +import app.revanced.patches.music.misc.settings.PreferenceScreen +import app.revanced.patches.music.misc.settings.settingsPatch import app.revanced.patches.music.misc.spoof.spoofVideoStreamsPatch import app.revanced.patches.shared.castContextFetchFingerprint import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch +import app.revanced.patches.shared.misc.settings.preference.IntentPreference import app.revanced.patches.shared.primeMethodFingerprint @Suppress("unused") @@ -33,4 +38,23 @@ private fun gmsCoreSupportResourcePatch( toPackageName = REVANCED_MUSIC_PACKAGE_NAME, gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption, spoofedPackageSignature = "afb0fed5eeaebdd86f56a97742f4b6b33ef59875", -) + executeBlock = { + addResources("shared", "misc.gms.gmsCoreSupportResourcePatch") + + val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption + + PreferenceScreen.MISC.addPreferences( + IntentPreference( + "microg_settings", + intent = IntentPreference.Intent("", "org.microg.gms.ui.SettingsActivity") { + "$gmsCoreVendorGroupId.android.gms" + } + ) + ) + } +) { + dependsOn( + addResourcesPatch, + settingsPatch + ) +} diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/settings/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/settings/Fingerprints.kt new file mode 100644 index 000000000..580e6e767 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/settings/Fingerprints.kt @@ -0,0 +1,11 @@ +package app.revanced.patches.music.misc.settings + +import app.revanced.patcher.fingerprint + +internal val googleApiActivityFingerprint = fingerprint { + returns("V") + parameters("Landroid/os/Bundle;") + custom { method, classDef -> + classDef.endsWith("GoogleApiActivity;") && method.name == "onCreate" + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/settings/SettingsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/settings/SettingsPatch.kt new file mode 100644 index 000000000..09fab446b --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/settings/SettingsPatch.kt @@ -0,0 +1,176 @@ +package app.revanced.patches.music.misc.settings + +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patcher.patch.resourcePatch +import app.revanced.patches.all.misc.packagename.setOrGetFallbackPackageName +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.music.misc.extension.sharedExtensionPatch +import app.revanced.patches.shared.misc.mapping.resourceMappingPatch +import app.revanced.patches.shared.misc.settings.preference.* +import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference +import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting +import app.revanced.patches.shared.misc.settings.preference.SwitchPreference +import app.revanced.patches.shared.misc.settings.settingsPatch +import app.revanced.util.* +import com.android.tools.smali.dexlib2.util.MethodUtil + +private const val BASE_ACTIVITY_HOOK_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/shared/settings/BaseActivityHook;" +private const val GOOGLE_API_ACTIVITY_HOOK_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/music/settings/GoogleApiActivityHook;" + +private val preferences = mutableSetOf() + + +private val settingsResourcePatch = resourcePatch { + dependsOn( + resourceMappingPatch, + settingsPatch( + IntentPreference( + titleKey = "revanced_settings_title", + summaryKey = null, + intent = newIntent("revanced_settings_intent"), + ) to "settings_headers", + preferences + ) + ) + + execute { + + // TODO: Remove this when search will be abstract. + copyResources( + "settings", + ResourceGroup( + "layout", + "revanced_music_settings_with_toolbar.xml" + ) + ) + + val targetResource = "values/styles.xml" + inputStreamFromBundledResource( + "settings/music", + targetResource, + )!!.let { inputStream -> + "resources".copyXmlNode( + document(inputStream), + document("res/$targetResource"), + ).close() + } + + // Remove horizontal divider from the settings Preferences. + val styleFile = get("res/values/styles.xml") + styleFile.writeText( + styleFile.readText() + .replace( + "allowDividerAbove\">true", + "allowDividerAbove\">false" + ).replace( + "allowDividerBelow\">true", + "allowDividerBelow\">false" + ) + ) + } +} + +val settingsPatch = bytecodePatch( + description = "Adds settings for ReVanced to YouTube Music.", +) { + dependsOn( + sharedExtensionPatch, + settingsResourcePatch, + addResourcesPatch, + ) + + execute { + addResources("music", "misc.settings.settingsPatch") + addResources("shared", "misc.debugging.enableDebuggingPatch") + + // Should make a separate debugging patch, but for now include it with all installations. + PreferenceScreen.MISC.addPreferences( + PreferenceScreenPreference( + key = "revanced_debug_screen", + sorting = Sorting.UNSORTED, + preferences = setOf( + SwitchPreference("revanced_debug"), + NonInteractivePreference( + "revanced_debug_export_logs_to_clipboard", + tag = "app.revanced.extension.shared.settings.preference.ExportLogToClipboardPreference", + selectable = true + ), + NonInteractivePreference( + "revanced_debug_logs_clear_buffer", + tag = "app.revanced.extension.shared.settings.preference.ClearLogBufferPreference", + selectable = true + ) + ) + ) + ) + + // Add an "About" preference to the top. + preferences += NonInteractivePreference( + key = "revanced_settings_music_screen_0_about", + summaryKey = null, + tag = "app.revanced.extension.shared.settings.preference.ReVancedAboutPreference", + selectable = true, + ) + + // Modify GoogleApiActivity and remove all existing layout code. + // Must modify an existing activity and cannot add a new activity to the manifest, + // as that fails for root installations. + + googleApiActivityFingerprint.method.addInstructions( + 1, + """ + invoke-static { }, $GOOGLE_API_ACTIVITY_HOOK_CLASS_DESCRIPTOR->createInstance()Lapp/revanced/extension/music/settings/GoogleApiActivityHook; + move-result-object v0 + invoke-static { v0, p0 }, $BASE_ACTIVITY_HOOK_CLASS_DESCRIPTOR->initialize(Lapp/revanced/extension/shared/settings/BaseActivityHook;Landroid/app/Activity;)V + return-void + """ + ) + + // Remove other methods as they will break as the onCreate method is modified above. + googleApiActivityFingerprint.classDef.apply { + methods.removeIf { it.name != "onCreate" && !MethodUtil.isConstructor(it) } + } + } + + finalize { + PreferenceScreen.close() + } +} + +/** + * Creates an intent to open ReVanced settings. + */ +fun newIntent(settingsName: String) = IntentPreference.Intent( + data = settingsName, + targetClass = "com.google.android.gms.common.api.GoogleApiActivity" +) { + // The package name change has to be reflected in the intent. + setOrGetFallbackPackageName("com.google.android.apps.youtube.music") +} + +object PreferenceScreen : BasePreferenceScreen() { + val ADS = Screen( + "revanced_settings_music_screen_1_ads", + summaryKey = null + ) + val GENERAL = Screen( + "revanced_settings_music_screen_2_general", + summaryKey = null + ) + val PLAYER = Screen( + "revanced_settings_music_screen_3_player", + summaryKey = null + ) + val MISC = Screen( + "revanced_settings_music_screen_4_misc", + summaryKey = null + ) + + override fun commit(screen: PreferenceScreenPreference) { + preferences += screen + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/SpoofVideoStreams.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/SpoofVideoStreamsPatch.kt similarity index 51% rename from patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/SpoofVideoStreams.kt rename to patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/SpoofVideoStreamsPatch.kt index e50a70a49..7d1a4c648 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/SpoofVideoStreams.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/SpoofVideoStreamsPatch.kt @@ -1,12 +1,19 @@ package app.revanced.patches.music.misc.spoof import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.music.misc.extension.sharedExtensionPatch import app.revanced.patches.music.misc.gms.musicActivityOnCreateFingerprint +import app.revanced.patches.music.misc.settings.PreferenceScreen +import app.revanced.patches.music.misc.settings.settingsPatch import app.revanced.patches.music.playservice.is_7_33_or_greater import app.revanced.patches.music.playservice.is_8_11_or_greater import app.revanced.patches.music.playservice.is_8_15_or_greater import app.revanced.patches.music.playservice.versionCheckPatch +import app.revanced.patches.shared.misc.settings.preference.ListPreference +import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference +import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.spoof.spoofVideoStreamsPatch private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/spoof/SpoofVideoStreamsPatch;" @@ -16,17 +23,36 @@ val spoofVideoStreamsPatch = spoofVideoStreamsPatch( fixMediaFetchHotConfigAlternativeChanges = { is_8_11_or_greater && !is_8_15_or_greater }, fixParsePlaybackResponseFeatureFlag = { is_7_33_or_greater }, block = { + dependsOn( + sharedExtensionPatch, + settingsPatch, + addResourcesPatch, + versionCheckPatch, + userAgentClientSpoofPatch + ) + compatibleWith( "com.google.android.apps.youtube.music"( "7.29.52" ) ) - - dependsOn(sharedExtensionPatch, versionCheckPatch, userAgentClientSpoofPatch) }, executeBlock = { + addResources("shared", "misc.spoof.spoofVideoStreamsPatch") + + PreferenceScreen.MISC.addPreferences( + PreferenceScreenPreference( + key = "revanced_spoof_video_streams_screen", + sorting = PreferenceScreenPreference.Sorting.UNSORTED, + preferences = setOf( + SwitchPreference("revanced_spoof_video_streams"), + ListPreference("revanced_spoof_video_streams_client_type"), + ) + ) + ) + musicActivityOnCreateFingerprint.method.addInstruction( - 1, // Must use 1 index so context is set by extension patch. + 0, "invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->setClientOrderToUse()V" ) } diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/SpoofVideoStreamsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/SpoofVideoStreamsPatch.kt index 923089d01..26b80a2f9 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/SpoofVideoStreamsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/SpoofVideoStreamsPatch.kt @@ -9,8 +9,8 @@ import app.revanced.patcher.patch.BytecodePatchBuilder import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable +import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResourcesPatch -import app.revanced.patches.music.misc.extension.sharedExtensionPatch import app.revanced.util.findFreeRegister import app.revanced.util.findInstructionIndicesReversedOrThrow import app.revanced.util.getReference @@ -46,6 +46,8 @@ fun spoofVideoStreamsPatch( dependsOn(addResourcesPatch) execute { + addResources("shared", "misc.fix.playback.spoofVideoStreamsPatch") + // region Enable extension helper method used by other patches patchIncludedExtensionMethodFingerprint.method.returnEarly(true) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt index 6d5b70905..ab8c54afb 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt @@ -22,6 +22,8 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/EnableDebuggingPatch;" +// TODO: Refactor this into a shared patch that can be used by both YT and YT Music. +// Almost all of the feature flag hooks are the same between both apps. val enableDebuggingPatch = bytecodePatch( name = "Enable debugging", description = "Adds options for debugging and exporting ReVanced logs to the clipboard.", @@ -45,6 +47,7 @@ val enableDebuggingPatch = bytecodePatch( ) execute { + addResources("shared", "misc.debugging.enableDebuggingPatch") addResources("youtube", "misc.debugging.enableDebuggingPatch") PreferenceScreen.MISC.addPreferences( @@ -58,13 +61,13 @@ val enableDebuggingPatch = bytecodePatch( SwitchPreference("revanced_debug_toast_on_error"), NonInteractivePreference( "revanced_debug_export_logs_to_clipboard", - tag = "app.revanced.extension.youtube.settings.preference.ExportLogToClipboardPreference", - selectable = true, + tag = "app.revanced.extension.shared.settings.preference.ExportLogToClipboardPreference", + selectable = true ), NonInteractivePreference( "revanced_debug_logs_clear_buffer", - tag = "app.revanced.extension.youtube.settings.preference.ClearLogBufferPreference", - selectable = true, + tag = "app.revanced.extension.shared.settings.preference.ClearLogBufferPreference", + selectable = true ), ), ), diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch.kt index 3c8b238cc..5ffd49cb4 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch.kt @@ -53,7 +53,7 @@ private fun gmsCoreSupportResourcePatch( gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption, spoofedPackageSignature = "24bb24c05e47e0aefa68a58a766179d9b613a600", executeBlock = { - addResources("youtube", "misc.gms.gmsCoreSupportResourcePatch") + addResources("shared", "misc.gms.gmsCoreSupportResourcePatch") val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption @@ -62,10 +62,14 @@ private fun gmsCoreSupportResourcePatch( "microg_settings", intent = IntentPreference.Intent("", "org.microg.gms.ui.SettingsActivity") { "$gmsCoreVendorGroupId.android.gms" - }, - ), + } + ) ) - }, + } ) { - dependsOn(settingsPatch, addResourcesPatch, accountCredentialsInvalidTextPatch) + dependsOn( + addResourcesPatch, + settingsPatch, + accountCredentialsInvalidTextPatch + ) } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt index 672999b6a..a90bb9163 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt @@ -29,7 +29,9 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethod import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter import com.android.tools.smali.dexlib2.util.MethodUtil -private const val EXTENSION_CLASS_DESCRIPTOR = +private const val BASE_ACTIVITY_HOOK_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/shared/settings/BaseActivityHook;" +private const val LICENSE_ACTIVITY_HOOK_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/settings/LicenseActivityHook;" internal var appearanceStringId = -1L @@ -37,10 +39,6 @@ internal var appearanceStringId = -1L private val preferences = mutableSetOf() -fun addSettingPreference(screen: BasePreference) { - preferences += screen -} - private val settingsResourcePatch = resourcePatch { dependsOn( resourceMappingPatch, @@ -225,7 +223,9 @@ val settingsPatch = bytecodePatch( licenseActivityOnCreateFingerprint.method.addInstructions( 1, """ - invoke-static { p0 }, $EXTENSION_CLASS_DESCRIPTOR->initialize(Landroid/app/Activity;)V + invoke-static {}, $LICENSE_ACTIVITY_HOOK_CLASS_DESCRIPTOR->createInstance()Lapp/revanced/extension/youtube/settings/LicenseActivityHook; + move-result-object v0 + invoke-static { v0, p0 }, $BASE_ACTIVITY_HOOK_CLASS_DESCRIPTOR->initialize(Lapp/revanced/extension/shared/settings/BaseActivityHook;Landroid/app/Activity;)V return-void """ ) @@ -249,7 +249,7 @@ val settingsPatch = bytecodePatch( ).toMutable().apply { addInstructions( """ - invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->getAttachBaseContext(Landroid/content/Context;)Landroid/content/Context; + invoke-static { p1 }, $LICENSE_ACTIVITY_HOOK_CLASS_DESCRIPTOR->getAttachBaseContext(Landroid/content/Context;)Landroid/content/Context; move-result-object p1 invoke-super { p0, p1 }, $superclass->attachBaseContext(Landroid/content/Context;)V return-void @@ -294,7 +294,7 @@ val settingsPatch = bytecodePatch( addInstructions( """ invoke-super { p0, p1 }, Landroid/app/Activity;->onConfigurationChanged(Landroid/content/res/Configuration;)V - invoke-static { p0, p1 }, $EXTENSION_CLASS_DESCRIPTOR->handleConfigurationChanged(Landroid/app/Activity;Landroid/content/res/Configuration;)V + invoke-static { p0, p1 }, $LICENSE_ACTIVITY_HOOK_CLASS_DESCRIPTOR->handleConfigurationChanged(Landroid/app/Activity;Landroid/content/res/Configuration;)V return-void """ ) @@ -309,15 +309,15 @@ val settingsPatch = bytecodePatch( val register = getInstruction(index).registerA addInstructionsAtControlFlowLabel( index, - "invoke-static { v$register }, ${EXTENSION_CLASS_DESCRIPTOR}->updateLightDarkModeStatus(Ljava/lang/Enum;)V", + "invoke-static { v$register }, ${LICENSE_ACTIVITY_HOOK_CLASS_DESCRIPTOR}->updateLightDarkModeStatus(Ljava/lang/Enum;)V", ) } } - // Add setting to force cairo settings fragment on/off. + // Add setting to force Cairo settings fragment on/off. cairoFragmentConfigFingerprint.method.insertLiteralOverride( CAIRO_CONFIG_LITERAL_VALUE, - "$EXTENSION_CLASS_DESCRIPTOR->useCairoSettingsFragment(Z)Z" + "$LICENSE_ACTIVITY_HOOK_CLASS_DESCRIPTOR->useCairoSettingsFragment(Z)Z" ) } diff --git a/patches/src/main/resources/addresources/values/arrays.xml b/patches/src/main/resources/addresources/values/arrays.xml index ac8860273..c8796f57d 100644 --- a/patches/src/main/resources/addresources/values/arrays.xml +++ b/patches/src/main/resources/addresources/values/arrays.xml @@ -121,18 +121,18 @@ ZH - - Android VR - VisionOS + visionOS ANDROID_VR_1_61_48 VISIONOS + + @string/revanced_swipe_overlay_style_entry_1 diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index a297dd20e..2f4840a5f 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -124,6 +124,8 @@ To translate new languages visit translate.revanced.app" and changes made here must also be made there. --> + GmsCore Settings + Settings for GmsCore MicroG GmsCore is not installed. Install it. Action needed @@ -140,6 +142,37 @@ Disabling battery optimizations for MicroG will not negatively affect battery us Tap the continue button and allow optimization changes." Continue + + Spoof video streams + Spoof the client video streams to prevent playback issues + Spoof video streams + Spoof the client video streams to prevent playback issues + Spoof video streams + "Video streams are spoofed + +If you are a YouTube Premium user, this setting may not be required" + "Video streams are not spoofed + +Playback may not work" + Turning off this setting may cause playback issues. + Default client + + + Debugging + Enable or disable debugging options + Debug logging + Debug logs are enabled + Debug logs are disabled + Export debug logs + Copies ReVanced debug logs to the clipboard + Debug logging is disabled + No logs found + Logs copied + Failed to export logs: %s + Clear debug logs + Clears all stored ReVanced debug logs + Logs cleared + @@ -168,11 +201,6 @@ Tap the continue button and allow optimization changes." Shorts background play is enabled - Debugging - Enable or disable debugging options - Debug logging - Debug logs are enabled - Debug logs are disabled Log protocol buffer Debug logs include proto buffer Debug logs do not include proto buffer @@ -190,15 +218,6 @@ However, enabling this will also log some user data such as your IP address.""Turning off error toasts hides all ReVanced error notifications. You will not be notified of any unexpected events." - Export debug logs - Copies ReVanced debug logs to the clipboard - Debug logging is disabled - No logs found - Logs copied - Failed to export logs: %s - Clear debug logs - Clears all stored ReVanced debug logs - Logs cleared Hide album cards @@ -1493,10 +1512,6 @@ Higher video qualities might be unlocked but you may experience video playback s Enabling this can unlock higher video qualities" Enabling this can cause video playback stuttering, worse battery life, and unknown side effects. - - GmsCore Settings - Settings for GmsCore - Haptic feedback Change haptic feedback @@ -1610,17 +1625,6 @@ Enabling this can unlock higher video qualities" Slide to seek is not enabled - Spoof video streams - Spoof the client video streams to prevent playback issues - Spoof video streams - "Video streams are spoofed - -If you are a YouTube Premium user, this setting may not be required" - "Video streams are not spoofed - -Video playback may not work" - Turning off this setting may cause video playback issues. - Default client Spoofing side effects Android spoofing side effects "• Audio track menu is missing @@ -1634,6 +1638,40 @@ Video playback may not work" Audio stream language + + + About + Ads + General + Player + Miscellaneous + + + Hide video ads + Video ads are hidden + Video ads are shown + + + Enable permanent repeat + Permanent repeat is enabled + Permanent repeat is disabled + + + Hide category bar + Category bar is hidden + Category bar is shown + + + Hide \'Get Music Premium\' label + Label is hidden + Label is shown + + + Hide upgrade button + Button is hidden + Button is shown + + Block audio ads diff --git a/patches/src/main/resources/settings/layout/revanced_music_settings_with_toolbar.xml b/patches/src/main/resources/settings/layout/revanced_music_settings_with_toolbar.xml new file mode 100644 index 000000000..09ce00ad4 --- /dev/null +++ b/patches/src/main/resources/settings/layout/revanced_music_settings_with_toolbar.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + diff --git a/patches/src/main/resources/settings/music/values/styles.xml b/patches/src/main/resources/settings/music/values/styles.xml new file mode 100644 index 000000000..4a692559a --- /dev/null +++ b/patches/src/main/resources/settings/music/values/styles.xml @@ -0,0 +1,7 @@ + + + +