diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java index e978301a65..18c6959aa4 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java @@ -373,6 +373,29 @@ public class Utils { return getContext().getResources().getStringArray(getResourceIdentifierOrThrow(ResourceType.ARRAY, resourceIdentifierName)); } + /** + * Checks if a specific app package is installed and enabled on the device. + * + * @param packageName The application package name to check (e.g., "app.morphe.android.apps.youtube.music"). + * @return True if the package is installed and enabled, false otherwise. + */ + public static boolean isPackageEnabled(String packageName) { + Context context = getContext(); + if (context == null || !isNotEmpty(packageName)) { + return false; + } + + try { + PackageManager pm = context.getPackageManager(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return pm.getApplicationInfo(packageName, PackageManager.ApplicationInfoFlags.of(0)).enabled; + } else { + return pm.getApplicationInfo(packageName, 0).enabled; + } + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } public interface MatchFilter { boolean matches(T object); } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/OverrideOpenInYouTubeMusicButtonPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/OverrideOpenInYouTubeMusicButtonPatch.java new file mode 100644 index 0000000000..8e12b40482 --- /dev/null +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/OverrideOpenInYouTubeMusicButtonPatch.java @@ -0,0 +1,60 @@ +package app.revanced.extension.youtube.patches; + + +import android.content.Intent; +import android.net.Uri; + +import androidx.annotation.Nullable; + +import app.revanced.extension.shared.Utils; +import app.revanced.extension.youtube.settings.Settings; + +@SuppressWarnings("unused") +public class OverrideOpenInYouTubeMusicButtonPatch { + + private static final String YOUTUBE_MUSIC_PACKAGE_NAME = "com.google.android.apps.youtube.music"; + + private static final Boolean overrideButton = Settings.OVERRIDE_OPEN_IN_YOUTUBE_MUSIC_BUTTON.get(); + + private static final String overridePackageName = getOverridePackageName(); + + @SuppressWarnings("SameReturnValue") + public static String getOverridePackageName() { + return ""; // Value is replaced during patching. + } + + public static @Nullable Intent overrideSetPackage(@Nullable Intent intent, @Nullable String packageName) { + if (intent == null || !overrideButton) return intent; + + if (YOUTUBE_MUSIC_PACKAGE_NAME.equals(packageName)) { + if (Utils.isNotEmpty(overridePackageName) && Utils.isPackageEnabled(overridePackageName)) { + return intent.setPackage(overridePackageName); + } + + return intent.setPackage(null); + } + + return intent.setPackage(packageName); + } + + public static @Nullable Intent overrideSetData(@Nullable Intent intent, @Nullable Uri uri) { + if (intent == null || uri == null || !overrideButton) return intent; + + String uriString = uri.toString(); + if (uriString.contains(YOUTUBE_MUSIC_PACKAGE_NAME)) { + if ("market".equals(uri.getScheme()) || uriString.contains("play.google.com/store/apps")) { + intent.setData(Uri.parse("https://music.youtube.com/")); + + if (Utils.isNotEmpty(overridePackageName) && Utils.isPackageEnabled(overridePackageName)) { + intent.setPackage(overridePackageName); + } else { + intent.setPackage(null); + } + + return intent; + } + } + + return intent.setData(uri); + } +} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java index d69ed54e2d..5bc9bfc6e4 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -296,6 +296,7 @@ public class Settings extends YouTubeAndMusicSettings { public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE, "revanced_remove_viewer_discretion_dialog_user_dialog_message"); public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", FALSE, true, "revanced_spoof_app_version_user_dialog_message"); + public static final BooleanSetting OVERRIDE_OPEN_IN_YOUTUBE_MUSIC_BUTTON = new BooleanSetting("revanced_override_open_in_youtube_music_button", TRUE, true); public static final EnumSetting CHANGE_START_PAGE = new EnumSetting<>("revanced_change_start_page", StartPage.DEFAULT, true); public static final BooleanSetting CHANGE_START_PAGE_ALWAYS = new BooleanSetting("revanced_change_start_page_always", FALSE, true, new ChangeStartPageTypeAvailability()); diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/Fingerprints.kt new file mode 100644 index 0000000000..3e6241754a --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/Fingerprints.kt @@ -0,0 +1,18 @@ +package app.revanced.patches.youtube.layout.buttons.music + +import app.revanced.patcher.accessFlags +import app.revanced.patcher.definingClass +import app.revanced.patcher.gettingFirstMethodDeclaratively +import app.revanced.patcher.name +import app.revanced.patcher.parameterTypes +import app.revanced.patcher.patch.BytecodePatchContext +import app.revanced.patcher.returnType +import com.android.tools.smali.dexlib2.AccessFlags + +internal val BytecodePatchContext.getOverridePackageNameMethod by gettingFirstMethodDeclaratively { + name("getOverridePackageName") + definingClass(EXTENSION_CLASS_DESCRIPTOR) + accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) + returnType("Ljava/lang/String;") + parameterTypes() +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/OverrideOpenInYouTubeMusicButtonPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/OverrideOpenInYouTubeMusicButtonPatch.kt new file mode 100644 index 0000000000..014472b9e7 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/music/OverrideOpenInYouTubeMusicButtonPatch.kt @@ -0,0 +1,122 @@ +package app.revanced.patches.youtube.layout.buttons.music + +import app.revanced.patcher.extensions.getInstruction +import app.revanced.patcher.extensions.methodReference +import app.revanced.patcher.extensions.replaceInstruction +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patcher.patch.resourcePatch +import app.revanced.patcher.patch.stringOption +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory +import app.revanced.patches.shared.misc.settings.preference.SwitchPreference +import app.revanced.patches.youtube.misc.settings.PreferenceScreen +import app.revanced.patches.youtube.misc.settings.settingsPatch +import app.revanced.util.forEachInstructionAsSequence +import app.revanced.util.returnEarly +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction +import com.android.tools.smali.dexlib2.iface.instruction.RegisterRangeInstruction +import org.w3c.dom.Element + +internal const val EXTENSION_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/youtube/patches/OverrideOpenInYouTubeMusicButtonPatch;" + +val packageNameOption = stringOption( + name = "YouTube Music package name", + description = "The package name of the YouTube Music app to open when clicking the 'Open in YouTube Music' button.", + default = "app.revanced.android.apps.youtube.music", + values = mapOf( + "Original package name" to "com.google.android.apps.youtube.music", + "ReVanced default package name" to "app.revanced.android.apps.youtube.music" + ), + required = true, +) + +private val overrideOpenInYouTubeMusicManifestResourcePatch = resourcePatch { + apply { + val packageName by packageNameOption + + document("AndroidManifest.xml").use { document -> + val queriesList = document.getElementsByTagName("queries") + + val queries = if (queriesList.length > 0) queriesList.item(0) as Element + else document.createElement("queries").also(document::appendChild) + + document.createElement("package").apply { + setAttribute("android:name", packageName) + }.let(queries::appendChild) + } + } +} + +@Suppress("unused") +val overrideOpenInYouTubeMusicButtonPatch = bytecodePatch( + name = "Override 'Open in YouTube Music' button", + description = "Overrides the button to open YouTube Music under a different package name. " + + "By default, it overrides to the ReVanced default package name of YouTube Music.", +) { + dependsOn( + settingsPatch, + addResourcesPatch, + overrideOpenInYouTubeMusicManifestResourcePatch + ) + + compatibleWith( + "com.google.android.youtube"( + "20.14.43", + "20.21.37", + "20.26.46", + "20.31.42", + "20.37.48", + "20.40.45" + ), + ) + + val packageName by packageNameOption() + + apply { + addResources("youtube", "layout.buttons.music.overrideOpenInYouTubeMusicButtonPatch") + + PreferenceScreen.GENERAL.addPreferences( + PreferenceCategory( + titleKey = null, + tag = "app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory", + preferences = setOf(SwitchPreference(key = "revanced_override_open_in_youtube_music_button")) + ) + ) + + getOverridePackageNameMethod.returnEarly(packageName!!) + + forEachInstructionAsSequence({ _, _, instruction, index -> + if (instruction.opcode != Opcode.INVOKE_VIRTUAL) return@forEachInstructionAsSequence null + val reference = instruction.methodReference ?: return@forEachInstructionAsSequence null + if (reference.definingClass != "Landroid/content/Intent;") return@forEachInstructionAsSequence null + + when (reference.name) { + "setPackage" if reference.parameterTypes == listOf("Ljava/lang/String;") -> + index to "overrideSetPackage(Landroid/content/Intent;Ljava/lang/String;)Landroid/content/Intent;" + + "setData" if reference.parameterTypes == listOf("Landroid/net/Uri;") -> + index to "overrideSetData(Landroid/content/Intent;Landroid/net/Uri;)Landroid/content/Intent;" + + else -> null + } + }) { method, (index, methodDescriptor) -> + val invokeString = when (val instruction = method.getInstruction(index)) { + is RegisterRangeInstruction -> + "invoke-static/range { v${instruction.startRegister} .. v${instruction.startRegister + instruction.registerCount - 1} }" + + is FiveRegisterInstruction -> + "invoke-static { v${instruction.registerC}, v${instruction.registerD} }" + + else -> return@forEachInstructionAsSequence + } + + method.replaceInstruction( + index, + "$invokeString, $EXTENSION_CLASS_DESCRIPTOR->$methodDescriptor" + ) + } + } +} diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index d6ddd2c484..6c05ad9dae 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1111,10 +1111,7 @@ To show the Audio track menu, change \'Spoof video streams\' to \'Android No SDK Hide end screen suggested video - "End screen suggested video is hidden when autoplay is turned off - -Autoplay can be changed in YouTube settings: -Settings → Playback → Autoplay next video" + "End screen suggested video is hidden End screen suggested video is shown @@ -1442,6 +1439,11 @@ If later turned off, it is recommended to clear the app data to prevent UI bugs. 20.13.41 - Restore non collapsed video action bar 20.05.46 - Restore transcript functionality + + Override \'Open in YouTube Music\' button + \'Open in YouTube Music\' button opens your target music app + \'Open in YouTube Music\' button opens the original app + Change start page Default