From 6555f6e6f8b52c2f1ddab1f52c6704cd2d8cfc12 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 11 Oct 2025 12:00:26 +0400 Subject: [PATCH 1/6] fix(YouTube - Custom branding): Do not add a broken custom icon if the user provides an invalid custom icon path --- .../layout/branding/CustomBrandingPatch.kt | 3 +- .../branding/BaseCustomBrandingPatch.kt | 250 +++++++++--------- 2 files changed, 132 insertions(+), 121 deletions(-) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/CustomBrandingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/CustomBrandingPatch.kt index 6f0f46026..b1c811de2 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/CustomBrandingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/CustomBrandingPatch.kt @@ -4,6 +4,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.util.smali.ExternalLabel +import app.revanced.patches.music.misc.extension.sharedExtensionPatch import app.revanced.patches.music.misc.gms.Constants.MUSIC_MAIN_ACTIVITY_NAME import app.revanced.patches.music.misc.gms.Constants.MUSIC_PACKAGE_NAME import app.revanced.patches.music.misc.gms.musicActivityOnCreateFingerprint @@ -68,7 +69,7 @@ val customBrandingPatch = baseCustomBrandingPatch( preferenceScreen = PreferenceScreen.GENERAL, block = { - dependsOn(disableSplashAnimationPatch) + dependsOn(sharedExtensionPatch, disableSplashAnimationPatch) compatibleWith( "com.google.android.apps.youtube.music"( diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/layout/branding/BaseCustomBrandingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/layout/branding/BaseCustomBrandingPatch.kt index d8356aee0..7b46b820f 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/layout/branding/BaseCustomBrandingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/layout/branding/BaseCustomBrandingPatch.kt @@ -21,7 +21,6 @@ import app.revanced.util.findElementByAttributeValueOrThrow import app.revanced.util.removeFromParent import app.revanced.util.returnEarly import org.w3c.dom.Element -import org.w3c.dom.Node import org.w3c.dom.NodeList import java.io.File import java.util.logging.Logger @@ -106,6 +105,7 @@ internal fun baseCustomBrandingPatch( dependsOn( addResourcesPatch, + bytecodePatch { execute { mainActivityOnCreateFingerprint.method.addInstruction( @@ -201,6 +201,135 @@ internal fun baseCustomBrandingPatch( ) } + document("AndroidManifest.xml").use { document -> + // Create launch aliases that can be programmatically selected in app. + fun createAlias( + aliasName: String, + iconMipmapName: String, + appNameIndex: Int, + useCustomName: Boolean, + enabled: Boolean, + intents: NodeList + ): Element { + val label = if (useCustomName) { + if (customName == null) { + "Custom" // Dummy text, and normally cannot be seen. + } else { + customName!! + } + } else if (appNameIndex == 1) { + // Indexing starts at 1. + originalAppName + } else { + "@string/revanced_custom_branding_name_entry_$appNameIndex" + } + val alias = document.createElement("activity-alias") + alias.setAttribute("android:name", aliasName) + alias.setAttribute("android:enabled", enabled.toString()) + alias.setAttribute("android:exported", "true") + alias.setAttribute("android:icon", "@mipmap/$iconMipmapName") + alias.setAttribute("android:label", label) + alias.setAttribute("android:targetActivity", mainActivityName) + + // Copy all intents from the original alias so long press actions still work. + if (copyExistingIntentsToAliases) { + for (i in 0 until intents.length) { + alias.appendChild( + intents.item(i).cloneNode(true) + ) + } + } else { + val intentFilter = document.createElement("intent-filter").apply { + val action = document.createElement("action") + action.setAttribute("android:name", "android.intent.action.MAIN") + appendChild(action) + + val category = document.createElement("category") + category.setAttribute("android:name", "android.intent.category.LAUNCHER") + appendChild(category) + } + alias.appendChild(intentFilter) + } + + return alias + } + + val application = document.getElementsByTagName("application").item(0) as Element + val intentFilters = document.childNodes.findElementByAttributeValueOrThrow( + "android:name", + activityAliasNameWithIntents + ).childNodes + + // The YT application name can appear in some places along side the system + // YouTube app, such as the settings app list and in the "open with" file picker. + // Because the YouTube app cannot be completely uninstalled and only disabled, + // use a custom name for this situation to disambiguate which app is which. + application.setAttribute( + "android:label", + "@string/revanced_custom_branding_name_entry_2" + ) + + for (appNameIndex in 1 .. numberOfPresetAppNames) { + fun aliasName(name: String): String = ".revanced_" + name + '_' + appNameIndex + + val useCustomNameLabel = (useCustomName && appNameIndex == numberOfPresetAppNames) + + // Original icon. + application.appendChild( + createAlias( + aliasName = aliasName(ORIGINAL_USER_ICON_STYLE_NAME), + iconMipmapName = originalLauncherIconName, + appNameIndex = appNameIndex, + useCustomName = useCustomNameLabel, + enabled = (appNameIndex == 1), + intentFilters + ) + ) + + // Bundled icons. + iconStyleNames.forEachIndexed { index, style -> + application.appendChild( + createAlias( + aliasName = aliasName(style), + iconMipmapName = LAUNCHER_RESOURCE_NAME_PREFIX + style, + appNameIndex = appNameIndex, + useCustomName = useCustomNameLabel, + enabled = false, + intentFilters + ) + ) + } + + // User provided custom icon. + // + // Must add all aliases even if the user did not provide a custom icon of their own. + // This is because if the user installs with an option, then repatches without the option, + // the alias must still exist because if it was previously enabled and then it's removed + // the app will become broken and cannot launch. Even if the app data is cleared + // it still cannot be launched and the only fix is to uninstall the app. + // To prevent this, always include all aliases and use dummy data if needed. + application.appendChild( + createAlias( + aliasName = aliasName(CUSTOM_USER_ICON_STYLE_NAME), + iconMipmapName = LAUNCHER_RESOURCE_NAME_PREFIX + CUSTOM_USER_ICON_STYLE_NAME, + appNameIndex = appNameIndex, + useCustomName = useCustomNameLabel, + enabled = false, + intentFilters + ) + ) + } + + // Remove the main action from the original alias, otherwise two apps icons + // can be shown in the launcher. Can only be done after adding the new aliases. + intentFilters.findElementByAttributeValueOrThrow( + "android:name", + "android.intent.action.MAIN" + ).removeFromParent() + } + + // Copy custom icons last, so if the user enters an invalid icon path + // and an exception is thrown then the critical manifest changes are still made. if (useCustomIcon) { // Copy user provided files val iconPathFile = File(customIcon!!.trim()) @@ -263,125 +392,6 @@ internal fun baseCustomBrandingPatch( } } - document("AndroidManifest.xml").use { document -> - // Create launch aliases that can be programmatically selected in app. - fun createAlias( - aliasName: String, - iconMipmapName: String, - appNameIndex: Int, - useCustomName: Boolean, - enabled: Boolean, - intents: NodeList - ): Element { - val label = if (useCustomName) { - if (customName == null) { - "Custom" // Dummy text, and normally cannot be seen. - } else { - customName!! - } - } else if (appNameIndex == 1) { - // Indexing starts at 1. - originalAppName - } else { - "@string/revanced_custom_branding_name_entry_$appNameIndex" - } - val alias = document.createElement("activity-alias") - alias.setAttribute("android:name", aliasName) - alias.setAttribute("android:enabled", enabled.toString()) - alias.setAttribute("android:exported", "true") - alias.setAttribute("android:icon", "@mipmap/$iconMipmapName") - alias.setAttribute("android:label", label) - alias.setAttribute("android:targetActivity", mainActivityName) - - // Copy all intents from the original alias so long press actions still work. - if (copyExistingIntentsToAliases) { - for (i in 0 until intents.length) { - alias.appendChild( - intents.item(i).cloneNode(true) - ) - } - } else { - val intentFilter = document.createElement("intent-filter").apply { - val action = document.createElement("action") - action.setAttribute("android:name", "android.intent.action.MAIN") - appendChild(action) - - val category = document.createElement("category") - category.setAttribute("android:name", "android.intent.category.LAUNCHER") - appendChild(category) - } - alias.appendChild(intentFilter) - } - - return alias - } - - val intentFilters = document.childNodes.findElementByAttributeValueOrThrow( - "android:name", - activityAliasNameWithIntents - ).childNodes - - val application = document.getElementsByTagName("application").item(0) as Element - - for (appNameIndex in 1 .. numberOfPresetAppNames) { - fun aliasName(name: String): String = ".revanced_" + name + '_' + appNameIndex - - val useCustomNameLabel = (useCustomName && appNameIndex == numberOfPresetAppNames) - - // Original icon. - application.appendChild( - createAlias( - aliasName = aliasName(ORIGINAL_USER_ICON_STYLE_NAME), - iconMipmapName = originalLauncherIconName, - appNameIndex = appNameIndex, - useCustomName = useCustomNameLabel, - enabled = (appNameIndex == 1), - intentFilters - ) - ) - - // Bundled icons. - iconStyleNames.forEachIndexed { index, style -> - application.appendChild( - createAlias( - aliasName = aliasName(style), - iconMipmapName = LAUNCHER_RESOURCE_NAME_PREFIX + style, - appNameIndex = appNameIndex, - useCustomName = useCustomNameLabel, - enabled = false, - intentFilters - ) - ) - } - - // User provided custom icon. - // - // Must add all aliases even if the user did not provide a custom icon of their own. - // This is because if the user installs with an option, then repatches without the option, - // the alias must still exist because if it was previously enabled and then it's removed - // the app will become broken and cannot launch. Even if the app data is cleared - // it still cannot be launched and the only fix is to uninstall the app. - // To prevent this, always include all aliases and use dummy data if needed. - application.appendChild( - createAlias( - aliasName = aliasName(CUSTOM_USER_ICON_STYLE_NAME), - iconMipmapName = LAUNCHER_RESOURCE_NAME_PREFIX + CUSTOM_USER_ICON_STYLE_NAME, - appNameIndex = appNameIndex, - useCustomName = useCustomNameLabel, - enabled = false, - intentFilters - ) - ) - } - - // Remove the main action from the original alias, otherwise two apps icons - // can be shown in the launcher. Can only be done after adding the new aliases. - intentFilters.findElementByAttributeValueOrThrow( - "android:name", - "android.intent.action.MAIN" - ).removeFromParent() - } - executeBlock() } } From a8c4bdb8a6b81d94998740f4e721d86ab286f75e Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 11 Oct 2025 08:04:05 +0000 Subject: [PATCH 2/6] chore: Release v5.42.2-dev.3 [skip ci] ## [5.42.2-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.42.2-dev.2...v5.42.2-dev.3) (2025-10-11) ### Bug Fixes * **YouTube - Custom branding:** Do not add a broken custom icon if the user provides an invalid custom icon path ([6555f6e](https://github.com/ReVanced/revanced-patches/commit/6555f6e6f8b52c2f1ddab1f52c6704cd2d8cfc12)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 450f484bd..8c59946f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [5.42.2-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.42.2-dev.2...v5.42.2-dev.3) (2025-10-11) + + +### Bug Fixes + +* **YouTube - Custom branding:** Do not add a broken custom icon if the user provides an invalid custom icon path ([6555f6e](https://github.com/ReVanced/revanced-patches/commit/6555f6e6f8b52c2f1ddab1f52c6704cd2d8cfc12)) + ## [5.42.2-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.42.2-dev.1...v5.42.2-dev.2) (2025-10-10) diff --git a/gradle.properties b/gradle.properties index fea47f099..532d86a4f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M org.gradle.parallel = true android.useAndroidX = true kotlin.code.style = official -version = 5.42.2-dev.2 +version = 5.42.2-dev.3 From 50f0b9c5eee95ff5f9974e344802e1d2a4aab47b Mon Sep 17 00:00:00 2001 From: Swakshan <56347042+Swakshan@users.noreply.github.com> Date: Sat, 11 Oct 2025 16:47:24 +0530 Subject: [PATCH 3/6] feat(Instagram): Add `Hide suggested content` patch (#6075) --- patches/api/patches.api | 6 ++- .../instagram/hide/explore/Fingerprints.kt | 4 +- .../instagram/hide/explore/HideExploreFeed.kt | 40 +++++++++++-------- .../hide/suggestions/Fingerprints.kt | 17 ++++++++ .../hide/suggestions/HideSuggestedContent.kt | 19 +++++++++ 5 files changed, 67 insertions(+), 19 deletions(-) create mode 100644 patches/src/main/kotlin/app/revanced/patches/instagram/hide/suggestions/Fingerprints.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/instagram/hide/suggestions/HideSuggestedContent.kt diff --git a/patches/api/patches.api b/patches/api/patches.api index 15b955acb..dfc890bf2 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -269,7 +269,7 @@ public final class app/revanced/patches/instagram/feed/LimitFeedToFollowedProfil } public final class app/revanced/patches/instagram/hide/explore/HideExploreFeedKt { - public static final fun getHideExportFeedPatch ()Lapp/revanced/patcher/patch/BytecodePatch; + public static final fun getHideExploreFeedPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } public final class app/revanced/patches/instagram/hide/navigation/HideNavigationButtonsKt { @@ -280,6 +280,10 @@ public final class app/revanced/patches/instagram/hide/stories/HideStoriesKt { public static final fun getHideStoriesPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/instagram/hide/suggestions/HideSuggestedContentKt { + public static final fun getHideSuggestedContent ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/instagram/misc/devmenu/EnableDeveloperMenuPatchKt { public static final fun getEnableDeveloperMenuPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } diff --git a/patches/src/main/kotlin/app/revanced/patches/instagram/hide/explore/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/instagram/hide/explore/Fingerprints.kt index 63402788b..a85e8eb30 100644 --- a/patches/src/main/kotlin/app/revanced/patches/instagram/hide/explore/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/instagram/hide/explore/Fingerprints.kt @@ -3,7 +3,9 @@ package app.revanced.patches.instagram.hide.explore import app.revanced.patcher.fingerprint +internal const val EXPLORE_KEY_TO_BE_HIDDEN = "sectional_items" + internal val exploreResponseJsonParserFingerprint = fingerprint { - strings("sectional_items", "ExploreTopicalFeedResponse") + strings(EXPLORE_KEY_TO_BE_HIDDEN, "ExploreTopicalFeedResponse") custom { method, _ -> method.name == "parseFromJson" } } diff --git a/patches/src/main/kotlin/app/revanced/patches/instagram/hide/explore/HideExploreFeed.kt b/patches/src/main/kotlin/app/revanced/patches/instagram/hide/explore/HideExploreFeed.kt index 25ff17907..a2c7d5ba5 100644 --- a/patches/src/main/kotlin/app/revanced/patches/instagram/hide/explore/HideExploreFeed.kt +++ b/patches/src/main/kotlin/app/revanced/patches/instagram/hide/explore/HideExploreFeed.kt @@ -1,33 +1,39 @@ package app.revanced.patches.instagram.hide.explore +import app.revanced.patcher.Fingerprint import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction +import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.bytecodePatch import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +context(BytecodePatchContext) +internal fun Fingerprint.replaceJsonFieldWithBogus( + key: String, +) { + val targetStringIndex = stringMatches!!.first { match -> match.string == key }.index + val targetStringRegister = method.getInstruction(targetStringIndex).registerA + + /** + * Replacing the JSON key we want to skip with a random string that is not a valid JSON key. + * This way the feeds array will never be populated. + * Received JSON keys that are not handled are simply ignored, so there are no side effects. + */ + method.replaceInstruction( + targetStringIndex, + "const-string v$targetStringRegister, \"BOGUS\"", + ) +} + @Suppress("unused") -val hideExportFeedPatch = bytecodePatch( +val hideExploreFeedPatch = bytecodePatch( name = "Hide explore feed", description = "Hides posts and reels from the explore/search page.", - use = false + use = false, ) { compatibleWith("com.instagram.android") execute { - exploreResponseJsonParserFingerprint.method.apply { - val sectionalItemStringIndex = exploreResponseJsonParserFingerprint.stringMatches!!.first().index - val sectionalItemStringRegister = getInstruction(sectionalItemStringIndex).registerA - - /** - * Replacing the JSON key we want to skip with a random string that is not a valid JSON key. - * This way the feeds array will never be populated. - * Received JSON keys that are not handled are simply ignored, so there are no side effects. - */ - replaceInstruction( - sectionalItemStringIndex, - "const-string v$sectionalItemStringRegister, \"BOGUS\"" - ) - } + exploreResponseJsonParserFingerprint.replaceJsonFieldWithBogus(EXPLORE_KEY_TO_BE_HIDDEN) } } - diff --git a/patches/src/main/kotlin/app/revanced/patches/instagram/hide/suggestions/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/instagram/hide/suggestions/Fingerprints.kt new file mode 100644 index 000000000..0f731b4f4 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/instagram/hide/suggestions/Fingerprints.kt @@ -0,0 +1,17 @@ +package app.revanced.patches.instagram.hide.suggestions + +import app.revanced.patcher.fingerprint + +internal val FEED_ITEM_KEYS_TO_BE_HIDDEN = arrayOf( + "clips_netego", + "stories_netego", + "in_feed_survey", + "bloks_netego", + "suggested_igd_channels", + "suggested_top_accounts", + "suggested_users", +) + +internal val feedItemParseFromJsonFingerprint = fingerprint { + strings(*FEED_ITEM_KEYS_TO_BE_HIDDEN, "FeedItem") +} diff --git a/patches/src/main/kotlin/app/revanced/patches/instagram/hide/suggestions/HideSuggestedContent.kt b/patches/src/main/kotlin/app/revanced/patches/instagram/hide/suggestions/HideSuggestedContent.kt new file mode 100644 index 000000000..0c2501411 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/instagram/hide/suggestions/HideSuggestedContent.kt @@ -0,0 +1,19 @@ +package app.revanced.patches.instagram.hide.suggestions + +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.instagram.hide.explore.replaceJsonFieldWithBogus + +@Suppress("unused") +val hideSuggestedContent = bytecodePatch( + name = "Hide suggested content", + description = "Hides suggested stories, reels, threads and survey from feed (Suggested posts will still be shown).", + use = false, +) { + compatibleWith("com.instagram.android") + + execute { + FEED_ITEM_KEYS_TO_BE_HIDDEN.forEach { key -> + feedItemParseFromJsonFingerprint.replaceJsonFieldWithBogus(key) + } + } +} From 4547ecb73c81d6c3e5bc097d1c4e541ce94ac16f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 11 Oct 2025 11:22:05 +0000 Subject: [PATCH 4/6] chore: Release v5.43.0-dev.1 [skip ci] # [5.43.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.42.2-dev.3...v5.43.0-dev.1) (2025-10-11) ### Features * **Instagram:** Add `Hide suggested content` patch ([#6075](https://github.com/ReVanced/revanced-patches/issues/6075)) ([50f0b9c](https://github.com/ReVanced/revanced-patches/commit/50f0b9c5eee95ff5f9974e344802e1d2a4aab47b)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c59946f0..d363d1c4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [5.43.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.42.2-dev.3...v5.43.0-dev.1) (2025-10-11) + + +### Features + +* **Instagram:** Add `Hide suggested content` patch ([#6075](https://github.com/ReVanced/revanced-patches/issues/6075)) ([50f0b9c](https://github.com/ReVanced/revanced-patches/commit/50f0b9c5eee95ff5f9974e344802e1d2a4aab47b)) + ## [5.42.2-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.42.2-dev.2...v5.42.2-dev.3) (2025-10-11) diff --git a/gradle.properties b/gradle.properties index 532d86a4f..335db3e6b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M org.gradle.parallel = true android.useAndroidX = true kotlin.code.style = official -version = 5.42.2-dev.3 +version = 5.43.0-dev.1 From 5bd0f11630149588f26bdbca7e0f5a4999627cf4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 09:53:13 +0400 Subject: [PATCH 5/6] chore: Sync translations (#6117) --- .../addresources/values-ja-rJP/strings.xml | 36 +++++++++---------- .../addresources/values-vi-rVN/strings.xml | 24 ++++++------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/patches/src/main/resources/addresources/values-ja-rJP/strings.xml b/patches/src/main/resources/addresources/values-ja-rJP/strings.xml index 9f1636fb6..94c67bc1f 100644 --- a/patches/src/main/resources/addresources/values-ja-rJP/strings.xml +++ b/patches/src/main/resources/addresources/values-ja-rJP/strings.xml @@ -135,8 +135,8 @@ YouTube Premium ユーザーの場合、この設定は必要ない可能性が オリジナルの音声を強制的に使用 - オリジナルの音声トラック (言語) を使用します\n\nオートダビングを含む吹き替えの音声トラックは使用しません - アプリが選択した音声トラック (言語) を使用します\n\nオートダビングを含む吹き替えの音声トラックを使用する場合があります + オリジナルの音声トラック (言語) を使用します\n\nオートダビングを含む吹き替えの音声トラックは使用されません + アプリが選択した音声トラック (言語) を使用します\n\nオートダビングを含む吹き替えの音声トラックが使用される場合があります この機能を使用するには、「動画ストリームを偽装」のクライアントを Android Studio 以外の任意のクライアントに変更してください @@ -313,17 +313,17 @@ YouTube Premium ユーザーの場合、この設定は必要ない可能性が 登録チャンネルのコミュニティ ガイドラインは非表示です 登録チャンネルのコミュニティ ガイドラインは表示されます Timed Reaction を非表示 - Timed Reaction とチャット欄のハートマーク アイコンは表示されません - Timed Reaction とチャット欄のハートマー ク アイコンは表示されます + Timed reaction は表示されません\n\nライブ配信のチャット欄に常駐するハート マーク ボタンが非表示になります + Timed reaction は表示されます\n\nライブ配信のチャット欄にハート マーク ボタンが常駐します 「AI 生成による動画の要約」を非表示 「AI 生成による動画の要約」セクションは表示されません 「AI 生成による動画の要約」セクションは表示されます - 質問セクションを非表示 - 質問セクションは表示されません - 質問セクションは表示されます + 「質問する」を非表示 + 「質問する」セクションは表示されません + 「質問する」セクションは表示されます 付随情報を非表示 - 注目の場所 / ゲーム / 音楽 / 言及されている人物セクションは表示されません - 注目の場所 / ゲーム / 音楽 / 言及されている人物セクションは表示されます + 注目の場所 / ゲーム / 音楽 / 人物セクションは表示されません + 注目の場所 / ゲーム / 音楽 / 人物セクションは表示されます チャプター セクションを非表示 チャプター セクションは表示されません チャプター セクションは表示されます @@ -456,12 +456,12 @@ YouTube Premium ユーザーの場合、この設定は必要ない可能性が "ホーム / 登録チャンネル / 検索結果でキーワードに合致する動画を非表示にします 制限事項 -• ショート動画はチャンネル名で非表示にできない +• ショート動画はチャンネル名の合致では非表示にできない • 一部の UI コンポーネントが残ってしまう場合がある • キーワードを検索したとき、結果が表示されない場合がある" 単語全体で合致 - キーワードを二重引用符で囲むことで、動画のタイトルやチャンネル名の単語の一部とキーワードが合致しないようにできます<br><br>例えば、<br><b>\"ai\"</b>は、次の動画を非表示にします:<b>How does AI work?</b><br>しかし、次の動画は非表示にしません:<b>What does fair use mean?</b> + キーワードを二重引用符で囲むことで、動画のタイトルやチャンネル名に含まれる単語の一部分とキーワードが合致しないようにできます<br><br>例えば、<br><b>\"ai\"</b>は、次の動画を非表示にします:<b>How does AI work?</b><br>しかし、次の動画は非表示にしません:<b>What does fair use mean?</b> 使用できないキーワード: %s 二重引用符が必要なキーワード: %s @@ -741,7 +741,7 @@ YouTube Premium ユーザーの場合、この設定は必要ない可能性が フライアウト メニュー - プレーヤー オーバーレイの歯車ボタンから呼び出されるフライアウト メニューの項目を表示または非表示にします + プレーヤー オーバーレイの歯車アイコンボタンから呼び出されるフライアウト メニューの項目を表示または非表示にします 「字幕」を非表示 「字幕」は表示されません @@ -967,7 +967,7 @@ YouTube Premium ユーザーの場合、この設定は必要ない可能性が "終了画面の「関連動画」は表示されませんが、自動再生がオンの場合は関連動画が自動で再生されます 自動再生の設定は YouTube の設定で変更できます: -設定 → 再生 → 次の動画を自動再生" +[設定] → [再生] → [次の動画を自動再生]" 終了画面の「関連動画」は表示されます @@ -982,8 +982,8 @@ YouTube Premium ユーザーの場合、この設定は必要ない可能性が プレーヤー ポップアップ パネルを非表示 - プレーヤー ポップアップ パネルは表示されません\n\nプレイリストやチャット欄などを閉じた状態で動画を開きます - プレーヤー ポップアップ パネルは表示されます\n\nプレイリストやチャット欄などを展開した状態で動画を開きます + プレーヤー ポップアップ パネルは表示されません\n\nプレイリストやライブ配信のチャット欄などを閉じた状態で動画を開きます + プレーヤー ポップアップ パネルは表示されます\n\nプレイリストやライブ配信のチャット欄などを展開した状態で動画を開きます 再生終了時に全画面表示を解除 @@ -1369,7 +1369,7 @@ Automotive レイアウト ミニプレーヤーの種類 無効 デフォルト - 横長 (旧タイプ) + 旧タイプ (横長) タブレット 新タイプ 1 新タイプ 2 @@ -1381,8 +1381,8 @@ Automotive レイアウト ダブルタップとピンチによるサイズ変更を有効化 "ダブルタップとピンチによるサイズ変更は有効です -• ダブルタップすると、ミニプレーヤーのサイズが大きくなる -• もう一度ダブルタップすると、元のサイズに戻る" +• ダブルタップするとミニプレーヤーのサイズが大きくなる +• もう一度ダブルタップすると元のサイズに戻る" ダブルタップとピンチによるサイズ変更は無効です ドラッグ&ドロップを無効化 ドラッグ&ドロップは無効です diff --git a/patches/src/main/resources/addresources/values-vi-rVN/strings.xml b/patches/src/main/resources/addresources/values-vi-rVN/strings.xml index 0df40a32f..35f29f3a5 100644 --- a/patches/src/main/resources/addresources/values-vi-rVN/strings.xml +++ b/patches/src/main/resources/addresources/values-vi-rVN/strings.xml @@ -378,12 +378,12 @@ Nếu cài đặt này được bật và Doodle đang hiển thị tại khu v Nút Chuyển đến cửa hàng được hiển thị Bình luận Ẩn hoặc hiện các thành phần bình luận - Ẩn tóm tắt cuộc trò chuyện AI - Tóm tắt cuộc trò chuyện AI đã bị ẩn - Tóm tắt cuộc trò chuyện AI được hiển thị - Ẩn tóm tắt bình luận AI - Tóm tắt bình luận AI đã bị ẩn - Tóm tắt bình luận AI được hiển thị + Ẩn tóm tắt cuộc trò chuyện do AI tạo + Tóm tắt cuộc trò chuyện do AI tạo đã bị ẩn + Tóm tắt cuộc trò chuyện do AI tạo được hiển thị + Ẩn tóm tắt bình luận do AI tạo + Tóm tắt bình luận do AI tạo đã bị ẩn + Tóm tắt bình luận do AI tạo được hiển thị Ẩn nguyên tắc kênh Nguyên tắc kênh đã bị ẩn Nguyên tắc kênh được hiển thị @@ -912,12 +912,12 @@ Nếu thay đổi cài đặt này không có hiệu lực, hãy thử chuyển Ẩn nút Sắp diễn ra Nút sắp diễn ra đã bị ẩn Nút sắp diễn ra được hiển thị - Ẩn nút \"Sử dụng âm thanh này\" - Nút \"Sử dụng âm thanh này\" đã bị ẩn - Nút \"Sử dụng âm thanh này\" được hiển thị - Ẩn nút \"Sử dụng mẫu này\" - Nút \"Sử dụng mẫu này\" đã bị ẩn - Nút \"Sử dụng mẫu này\" được hiển thị + Ẩn nút Dùng âm thanh này + Nút dùng âm thanh này đã bị ẩn + Nút dùng âm thanh này được hiển thị + Ẩn nút Sử dụng mẫu này + Nút sử dụng mẫu này đã bị ẩn + Nút sử dụng mẫu này được hiển thị Ẩn hiệu ứng đài phun nút Thích Hiệu ứng đài phun nút thích đã bị ẩn Hiệu ứng đài phun nút thích được hiển thị From 10ea250d4a91f8ab3b7f865612a403fc93a857b5 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Tue, 14 Oct 2025 09:54:14 +0400 Subject: [PATCH 6/6] fix(YouTube - Custom branding): Use ReVanced icon for status bar notification icon (#6108) --- .../preference/MusicPreferenceFragment.java | 13 ++ .../shared/patches/CustomBrandingPatch.java | 59 +++++-- .../preference/ToolbarPreferenceFragment.java | 19 +++ .../preference/YouTubePreferenceFragment.java | 13 ++ .../layout/branding/CustomBrandingPatch.kt | 2 +- .../branding/BaseCustomBrandingPatch.kt | 150 +++++++++++++----- .../shared/layout/branding/Fingerprints.kt | 8 + .../layout/branding/CustomBrandingPatch.kt | 2 +- .../revanced_adaptive_foreground_minimal.xml | 62 ++++---- .../revanced_adaptive_foreground_rounded.xml | 98 +++++++----- .../revanced_adaptive_foreground_scaled.xml | 62 ++++---- .../revanced_adaptive_monochrome_custom.xml | 23 +-- .../revanced_adaptive_monochrome_minimal.xml | 23 +-- .../revanced_adaptive_monochrome_rounded.xml | 33 ++-- .../revanced_adaptive_monochrome_scaled.xml | 23 +-- .../drawable/revanced_notification_icon.xml | 12 ++ .../revanced_notification_icon_custom.xml | 12 ++ 17 files changed, 407 insertions(+), 207 deletions(-) create mode 100644 patches/src/main/resources/custom-branding/drawable/revanced_notification_icon.xml create mode 100644 patches/src/main/resources/custom-branding/drawable/revanced_notification_icon_custom.xml diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/preference/MusicPreferenceFragment.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/preference/MusicPreferenceFragment.java index 1ebae16df..86e517342 100644 --- a/extensions/music/src/main/java/app/revanced/extension/music/settings/preference/MusicPreferenceFragment.java +++ b/extensions/music/src/main/java/app/revanced/extension/music/settings/preference/MusicPreferenceFragment.java @@ -5,8 +5,10 @@ import android.preference.PreferenceScreen; import android.widget.Toolbar; import app.revanced.extension.music.settings.MusicActivityHook; +import app.revanced.extension.shared.GmsCoreSupport; 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.ToolbarPreferenceFragment; /** @@ -30,6 +32,17 @@ public class MusicPreferenceFragment extends ToolbarPreferenceFragment { preferenceScreen = getPreferenceScreen(); Utils.sortPreferenceGroups(preferenceScreen); setPreferenceScreenToolbar(preferenceScreen); + + // Clunky work around until preferences are custom classes that manage themselves. + // Custom branding only works with non-root install. But the preferences must be + // added during patched because of difficulties detecting during patching if it's + // a root install. So instead the non-functional preferences are removed during + // runtime if the app is mount (root) installation. + if (GmsCoreSupport.isPackageNameOriginal()) { + removePreferences( + BaseSettings.CUSTOM_BRANDING_ICON.key, + BaseSettings.CUSTOM_BRANDING_NAME.key); + } } catch (Exception ex) { Logger.printException(() -> "initialize failure", ex); } diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/CustomBrandingPatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/CustomBrandingPatch.java index 79a4d5484..c6cf761ca 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/CustomBrandingPatch.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/CustomBrandingPatch.java @@ -1,11 +1,14 @@ package app.revanced.extension.shared.patches; +import android.app.Notification; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; +import android.graphics.Color; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import app.revanced.extension.shared.GmsCoreSupport; import app.revanced.extension.shared.Logger; @@ -29,28 +32,56 @@ public class CustomBrandingPatch { // The most that can be done is to hide a theme from the UI and keep the alias with dummy data. public enum BrandingTheme { /** - * Original unpatched icon. Must be first enum. + * Original unpatched icon. */ - ORIGINAL("revanced_original"), - ROUNDED("revanced_rounded"), - MINIMAL("revanced_minimal"), - SCALED("revanced_scaled"), + ORIGINAL, + ROUNDED, + MINIMAL, + SCALED, /** - * User provided custom icon. Must be the last enum. + * User provided custom icon. */ - CUSTOM("revanced_custom"); - - public final String themeAlias; - - BrandingTheme(String themeAlias) { - this.themeAlias = themeAlias; - } + CUSTOM; private String packageAndNameIndexToClassAlias(String packageName, int appIndex) { if (appIndex <= 0) { throw new IllegalArgumentException("App index starts at index 1"); } - return packageName + '.' + themeAlias + '_' + appIndex; + return packageName + ".revanced_" + name().toLowerCase(Locale.US) + '_' + appIndex; + } + } + + private static final int notificationSmallIcon; + + static { + BrandingTheme branding = BaseSettings.CUSTOM_BRANDING_ICON.get(); + if (branding == BrandingTheme.ORIGINAL) { + notificationSmallIcon = 0; + } else { + // Original icon is quantum_ic_video_youtube_white_24 + String iconName = "revanced_notification_icon"; + if (branding == BrandingTheme.CUSTOM) { + iconName += "_custom"; + } + + notificationSmallIcon = Utils.getResourceIdentifier(iconName, "drawable"); + if (notificationSmallIcon == 0) { + Logger.printException(() -> "Could not load notification small icon"); + } + } + } + + /** + * Injection point. + */ + public static void setNotificationIcon(Notification.Builder builder) { + try { + if (notificationSmallIcon != 0) { + builder.setSmallIcon(notificationSmallIcon) + .setColor(Color.TRANSPARENT); // Remove YT red tint. + } + } catch (Exception ex) { + Logger.printException(() -> "setNotificationIcon failure", ex); } } 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 index 8f1d0975a..e299613dc 100644 --- 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 @@ -6,6 +6,7 @@ import android.graphics.Insets; import android.graphics.drawable.Drawable; import android.os.Build; import android.preference.Preference; +import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; import android.util.TypedValue; import android.view.ViewGroup; @@ -22,6 +23,24 @@ import app.revanced.extension.shared.settings.BaseActivityHook; @SuppressWarnings({"deprecation", "NewApi"}) public class ToolbarPreferenceFragment extends AbstractPreferenceFragment { + + /** + * Removes the list of preferences from this fragment, if they exist. + * @param keys Preference keys. + */ + protected void removePreferences(String ... keys) { + for (String key : keys) { + Preference pref = findPreference(key); + if (pref != null) { + PreferenceGroup parent = pref.getParent(); + if (parent != null) { + Logger.printDebug(() -> "Removing preference: " + key); + parent.removePreference(pref); + } + } + } + } + /** * Sets toolbar for all nested preference screens. */ diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/YouTubePreferenceFragment.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/YouTubePreferenceFragment.java index 1cd1c80c4..aec00487b 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/YouTubePreferenceFragment.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/YouTubePreferenceFragment.java @@ -4,8 +4,10 @@ import android.app.Dialog; import android.preference.PreferenceScreen; import android.widget.Toolbar; +import app.revanced.extension.shared.GmsCoreSupport; 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.ToolbarPreferenceFragment; import app.revanced.extension.youtube.settings.YouTubeActivityHook; @@ -30,6 +32,17 @@ public class YouTubePreferenceFragment extends ToolbarPreferenceFragment { preferenceScreen = getPreferenceScreen(); Utils.sortPreferenceGroups(preferenceScreen); setPreferenceScreenToolbar(preferenceScreen); + + // Clunky work around until preferences are custom classes that manage themselves. + // Custom branding only works with non-root install. But the preferences must be + // added during patched because of difficulties detecting during patching if it's + // a root install. So instead the non-functional preferences are removed during + // runtime if the app is mount (root) installation. + if (GmsCoreSupport.isPackageNameOriginal()) { + removePreferences( + BaseSettings.CUSTOM_BRANDING_ICON.key, + BaseSettings.CUSTOM_BRANDING_NAME.key); + } } catch (Exception ex) { Logger.printException(() -> "initialize failure", ex); } diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/CustomBrandingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/CustomBrandingPatch.kt index b1c811de2..7f7b49ca9 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/CustomBrandingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/CustomBrandingPatch.kt @@ -61,7 +61,7 @@ val customBrandingPatch = baseCustomBrandingPatch( originalLauncherIconName = "ic_launcher_release", originalAppName = "@string/app_launcher_name", originalAppPackageName = MUSIC_PACKAGE_NAME, - copyExistingIntentsToAliases = false, + isYouTubeMusic = true, numberOfPresetAppNames = 5, mainActivityOnCreateFingerprint = musicActivityOnCreateFingerprint, mainActivityName = MUSIC_MAIN_ACTIVITY_NAME, diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/layout/branding/BaseCustomBrandingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/layout/branding/BaseCustomBrandingPatch.kt index 7b46b820f..5381131f2 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/layout/branding/BaseCustomBrandingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/layout/branding/BaseCustomBrandingPatch.kt @@ -2,6 +2,7 @@ package app.revanced.patches.shared.layout.branding import app.revanced.patcher.Fingerprint import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.ResourcePatch import app.revanced.patcher.patch.ResourcePatchBuilder @@ -12,14 +13,24 @@ import app.revanced.patcher.patch.stringOption 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.shared.misc.mapping.resourceMappingPatch import app.revanced.patches.shared.misc.settings.preference.BasePreferenceScreen import app.revanced.patches.shared.misc.settings.preference.ListPreference import app.revanced.util.ResourceGroup import app.revanced.util.Utils.trimIndentMultiline +import app.revanced.util.addInstructionsAtControlFlowLabel import app.revanced.util.copyResources import app.revanced.util.findElementByAttributeValueOrThrow +import app.revanced.util.findInstructionIndicesReversedOrThrow +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow import app.revanced.util.removeFromParent import app.revanced.util.returnEarly +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction +import com.android.tools.smali.dexlib2.iface.reference.FieldReference +import com.android.tools.smali.dexlib2.iface.reference.TypeReference import org.w3c.dom.Element import org.w3c.dom.NodeList import java.io.File @@ -47,13 +58,15 @@ private const val LAUNCHER_RESOURCE_NAME_PREFIX = "revanced_launcher_" private const val LAUNCHER_ADAPTIVE_BACKGROUND_PREFIX = "revanced_adaptive_background_" private const val LAUNCHER_ADAPTIVE_FOREGROUND_PREFIX = "revanced_adaptive_foreground_" private const val LAUNCHER_ADAPTIVE_MONOCHROME_PREFIX = "revanced_adaptive_monochrome_" +private const val NOTIFICATION_ICON_NAME = "revanced_notification_icon" private val USER_CUSTOM_ADAPTIVE_FILE_NAMES = arrayOf( "$LAUNCHER_ADAPTIVE_BACKGROUND_PREFIX$CUSTOM_USER_ICON_STYLE_NAME.png", "$LAUNCHER_ADAPTIVE_FOREGROUND_PREFIX$CUSTOM_USER_ICON_STYLE_NAME.png" ) -private const val USER_CUSTOM_MONOCHROME_NAME = "$LAUNCHER_ADAPTIVE_MONOCHROME_PREFIX$CUSTOM_USER_ICON_STYLE_NAME.xml" +private const val USER_CUSTOM_MONOCHROME_FILE_NAME = "$LAUNCHER_ADAPTIVE_MONOCHROME_PREFIX$CUSTOM_USER_ICON_STYLE_NAME.xml" +private const val USER_CUSTOM_NOTIFICATION_ICON_FILE_NAME = "${NOTIFICATION_ICON_NAME}_$CUSTOM_USER_ICON_STYLE_NAME.xml" internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/shared/patches/CustomBrandingPatch;" @@ -65,7 +78,7 @@ internal fun baseCustomBrandingPatch( originalLauncherIconName: String, originalAppName: String, originalAppPackageName: String, - copyExistingIntentsToAliases: Boolean, + isYouTubeMusic: Boolean, numberOfPresetAppNames: Int, mainActivityOnCreateFingerprint: Fingerprint, mainActivityName: String, @@ -96,8 +109,9 @@ internal fun baseCustomBrandingPatch( Each of the folders must contain all of the following files: ${USER_CUSTOM_ADAPTIVE_FILE_NAMES.joinToString("\n")} - Optionally, the path can contain a 'drawable' folder with the monochrome icon file: - $USER_CUSTOM_MONOCHROME_NAME + Optionally, the path contains a 'drawable' folder with any of the monochrome icon files: + $USER_CUSTOM_MONOCHROME_FILE_NAME + $USER_CUSTOM_NOTIFICATION_ICON_FILE_NAME """.trimIndentMultiline() ) @@ -105,7 +119,7 @@ internal fun baseCustomBrandingPatch( dependsOn( addResourcesPatch, - + resourceMappingPatch, bytecodePatch { execute { mainActivityOnCreateFingerprint.method.addInstruction( @@ -114,25 +128,68 @@ internal fun baseCustomBrandingPatch( ) numberOfPresetAppNamesExtensionFingerprint.method.returnEarly(numberOfPresetAppNames) + + notificationFingerprint.method.apply { + val getBuilderIndex = if (isYouTubeMusic) { + // YT Music the field is not a plain object type. + indexOfFirstInstructionOrThrow { + getReference()?.type == "Landroid/app/Notification\$Builder;" + } + } else { + // Find the field name of the notification builder. Field is an Object type. + val builderCastIndex = indexOfFirstInstructionOrThrow { + val reference = getReference() + opcode == Opcode.CHECK_CAST && + reference?.type == "Landroid/app/Notification\$Builder;" + } + indexOfFirstInstructionReversedOrThrow(builderCastIndex) { + getReference()?.type == "Ljava/lang/Object;" + } + } + + val builderFieldName = getInstruction(getBuilderIndex) + .getReference() + + findInstructionIndicesReversedOrThrow( + Opcode.RETURN_VOID + ).forEach { index -> + addInstructionsAtControlFlowLabel( + index, + """ + move-object/from16 v0, p0 + iget-object v0, v0, $builderFieldName + check-cast v0, Landroid/app/Notification${'$'}Builder; + invoke-static { v0 }, $EXTENSION_CLASS_DESCRIPTOR->setNotificationIcon(Landroid/app/Notification${'$'}Builder;)V + """ + ) + } + } } - } + }, ) finalize { - val useCustomName = customName != null - val useCustomIcon = customIcon != null - - if (setOrGetFallbackPackageName(originalAppPackageName) == originalAppPackageName) { - if (useCustomName || useCustomIcon) { + // Can only check if app is root installation by checking if change package name patch is in use. + // and can only do that in the finalize block here. + // The UI preferences cannot be selectively added here, because the settings finalize block + // may have already run and the settings are already wrote to file. + // Instead, show a warning if any patch option was used (A rooted device launcher ignores the manifest changes), + // and the non-functional in-app settings are removed on app startup by extension code. + if (customName != null || customIcon != null) { + if (setOrGetFallbackPackageName(originalAppPackageName) == originalAppPackageName) { Logger.getLogger(this::class.java.name).warning( "Custom branding does not work with root installation. No changes applied." ) } - return@finalize } + } + + execute { + addResources("shared", "layout.branding.baseCustomBrandingPatch") + addResources(addResourcePatchName, "layout.branding.customBrandingPatch") preferenceScreen.addPreferences( - if (useCustomName) { + if (customName != null ) { ListPreference( key = "revanced_custom_branding_name", entriesKey = "revanced_custom_branding_name_custom_entries", @@ -141,7 +198,7 @@ internal fun baseCustomBrandingPatch( } else { ListPreference("revanced_custom_branding_name") }, - if (useCustomIcon) { + if (customIcon != null) { ListPreference( key = "revanced_custom_branding_icon", entriesKey = "revanced_custom_branding_icon_custom_entries", @@ -151,11 +208,6 @@ internal fun baseCustomBrandingPatch( ListPreference("revanced_custom_branding_icon") } ) - } - - execute { - addResources("shared", "layout.branding.baseCustomBrandingPatch") - addResources(addResourcePatchName, "layout.branding.customBrandingPatch") val useCustomName = customName != null val useCustomIcon = customIcon != null @@ -167,7 +219,7 @@ internal fun baseCustomBrandingPatch( "drawable", "$LAUNCHER_ADAPTIVE_BACKGROUND_PREFIX$style.xml", "$LAUNCHER_ADAPTIVE_FOREGROUND_PREFIX$style.xml", - "$LAUNCHER_ADAPTIVE_MONOCHROME_PREFIX$style.xml" + "$LAUNCHER_ADAPTIVE_MONOCHROME_PREFIX$style.xml", ), ResourceGroup( "mipmap-anydpi", @@ -176,20 +228,27 @@ internal fun baseCustomBrandingPatch( ) } - // Copy template user icon, because the aliases must be added even if no user icon is provided. copyResources( "custom-branding", - ResourceGroup( - "mipmap-anydpi", - "$LAUNCHER_RESOURCE_NAME_PREFIX$CUSTOM_USER_ICON_STYLE_NAME.xml", - ), + // Push notification 'small' icon. ResourceGroup( "drawable", - "$LAUNCHER_ADAPTIVE_MONOCHROME_PREFIX$CUSTOM_USER_ICON_STYLE_NAME.xml", + "$NOTIFICATION_ICON_NAME.xml" + ), + + // Copy template user icon, because the aliases must be added even if no user icon is provided. + ResourceGroup( + "drawable", + USER_CUSTOM_MONOCHROME_FILE_NAME, + USER_CUSTOM_NOTIFICATION_ICON_FILE_NAME + ), + ResourceGroup( + "mipmap-anydpi", + "$LAUNCHER_RESOURCE_NAME_PREFIX$CUSTOM_USER_ICON_STYLE_NAME.xml" ) ) - // Copy template icon png files. + // Copy template icon files. mipmapDirectories.forEach { dpi -> copyResources( "custom-branding", @@ -232,13 +291,7 @@ internal fun baseCustomBrandingPatch( alias.setAttribute("android:targetActivity", mainActivityName) // Copy all intents from the original alias so long press actions still work. - if (copyExistingIntentsToAliases) { - for (i in 0 until intents.length) { - alias.appendChild( - intents.item(i).cloneNode(true) - ) - } - } else { + if (isYouTubeMusic) { val intentFilter = document.createElement("intent-filter").apply { val action = document.createElement("action") action.setAttribute("android:name", "android.intent.action.MAIN") @@ -249,6 +302,12 @@ internal fun baseCustomBrandingPatch( appendChild(category) } alias.appendChild(intentFilter) + } else { + for (i in 0 until intents.length) { + alias.appendChild( + intents.item(i).cloneNode(true) + ) + } } return alias @@ -375,15 +434,20 @@ internal fun baseCustomBrandingPatch( } } - // Copy monochrome if it provided. - val monochromeRelativePath = "drawable/$USER_CUSTOM_MONOCHROME_NAME" - val monochromeFile = iconPathFile.resolve(monochromeRelativePath) - if (monochromeFile.exists()) { - monochromeFile.copyTo( - target = resourceDirectory.resolve(monochromeRelativePath), - overwrite = true - ) - copiedFiles = true + // Copy monochrome and small notification icon if it provided. + arrayOf( + USER_CUSTOM_MONOCHROME_FILE_NAME, + USER_CUSTOM_NOTIFICATION_ICON_FILE_NAME + ).forEach { fileName -> + val relativePath = "drawable/$fileName" + val file = iconPathFile.resolve(relativePath) + if (file.exists()) { + file.copyTo( + target = resourceDirectory.resolve(relativePath), + overwrite = true + ) + copiedFiles = true + } } if (!copiedFiles) { diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/layout/branding/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/layout/branding/Fingerprints.kt index c46cb1a88..8e99078d5 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/layout/branding/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/layout/branding/Fingerprints.kt @@ -11,3 +11,11 @@ internal val numberOfPresetAppNamesExtensionFingerprint = fingerprint { method.name == "numberOfPresetAppNames" && classDef.type == EXTENSION_CLASS_DESCRIPTOR } } + +// A much simpler fingerprint exists that can set the small icon (contains string "414843287017"), +// but that has limited usage and this fingerprint allows changing any part of the notification. +internal val notificationFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) + parameters("L") + strings("key_action_priority") +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/CustomBrandingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/CustomBrandingPatch.kt index 2294ef9b2..09ca7481d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/CustomBrandingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/CustomBrandingPatch.kt @@ -13,7 +13,7 @@ val customBrandingPatch = baseCustomBrandingPatch( originalLauncherIconName = "ic_launcher", originalAppName = "@string/application_name", originalAppPackageName = YOUTUBE_PACKAGE_NAME, - copyExistingIntentsToAliases = true, + isYouTubeMusic = false, numberOfPresetAppNames = 5, mainActivityOnCreateFingerprint = mainActivityOnCreateFingerprint, mainActivityName = YOUTUBE_MAIN_ACTIVITY_NAME, diff --git a/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_foreground_minimal.xml b/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_foreground_minimal.xml index a4b14512d..044733ee0 100644 --- a/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_foreground_minimal.xml +++ b/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_foreground_minimal.xml @@ -5,33 +5,39 @@ android:height="108dp" android:viewportWidth="256" android:viewportHeight="256"> - - - + - - - - - - - - - - + android:fillColor="#1B1B1B" + android:pathData="M0,0 L256,0 L256,256 L0,256 Z" /> + + + + + + + + + + + + diff --git a/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_foreground_rounded.xml b/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_foreground_rounded.xml index 1b4dcef31..a29891962 100644 --- a/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_foreground_rounded.xml +++ b/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_foreground_rounded.xml @@ -5,47 +5,59 @@ android:height="108dp" android:viewportWidth="800" android:viewportHeight="800"> - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_foreground_scaled.xml b/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_foreground_scaled.xml index e64200344..9a5e7944a 100644 --- a/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_foreground_scaled.xml +++ b/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_foreground_scaled.xml @@ -5,33 +5,39 @@ android:height="108dp" android:viewportWidth="256" android:viewportHeight="256"> - - - + - - - - - - - - - - + android:fillColor="#000000" + android:pathData="M0,0 L256,0 L256,256 L0,256 Z" /> + + + + + + + + + + + + diff --git a/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_monochrome_custom.xml b/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_monochrome_custom.xml index a5eadebdc..30c8d01bd 100644 --- a/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_monochrome_custom.xml +++ b/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_monochrome_custom.xml @@ -4,15 +4,16 @@ android:height="108dp" android:viewportWidth="256" android:viewportHeight="256"> - - - - + + + + diff --git a/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_monochrome_minimal.xml b/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_monochrome_minimal.xml index a5eadebdc..30c8d01bd 100644 --- a/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_monochrome_minimal.xml +++ b/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_monochrome_minimal.xml @@ -4,15 +4,16 @@ android:height="108dp" android:viewportWidth="256" android:viewportHeight="256"> - - - - + + + + diff --git a/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_monochrome_rounded.xml b/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_monochrome_rounded.xml index 11979dd6a..116f2da5d 100644 --- a/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_monochrome_rounded.xml +++ b/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_monochrome_rounded.xml @@ -4,20 +4,21 @@ android:height="108dp" android:viewportWidth="800" android:viewportHeight="800"> - - - - - + + + + + diff --git a/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_monochrome_scaled.xml b/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_monochrome_scaled.xml index 41160d4ba..381c59c8c 100644 --- a/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_monochrome_scaled.xml +++ b/patches/src/main/resources/custom-branding/drawable/revanced_adaptive_monochrome_scaled.xml @@ -4,15 +4,16 @@ android:height="108dp" android:viewportWidth="256" android:viewportHeight="256"> - - - - + + + + diff --git a/patches/src/main/resources/custom-branding/drawable/revanced_notification_icon.xml b/patches/src/main/resources/custom-branding/drawable/revanced_notification_icon.xml new file mode 100644 index 000000000..942b01f5f --- /dev/null +++ b/patches/src/main/resources/custom-branding/drawable/revanced_notification_icon.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/patches/src/main/resources/custom-branding/drawable/revanced_notification_icon_custom.xml b/patches/src/main/resources/custom-branding/drawable/revanced_notification_icon_custom.xml new file mode 100644 index 000000000..942b01f5f --- /dev/null +++ b/patches/src/main/resources/custom-branding/drawable/revanced_notification_icon_custom.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file