feat(YouTube): Premium heading patches has been integrated into Toolbar components patch

This commit is contained in:
inotia00
2024-04-27 05:33:41 +09:00
parent 9d78f19b74
commit a4811554a4
9 changed files with 151 additions and 77 deletions

View File

@ -11,7 +11,10 @@ import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPat
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.shared.voicesearch.VoiceSearchUtils.patchXml import app.revanced.patches.shared.voicesearch.VoiceSearchUtils.patchXml
import app.revanced.patches.youtube.general.toolbar.fingerprints.AttributeResolverFingerprint
import app.revanced.patches.youtube.general.toolbar.fingerprints.CreateSearchSuggestionsFingerprint import app.revanced.patches.youtube.general.toolbar.fingerprints.CreateSearchSuggestionsFingerprint
import app.revanced.patches.youtube.general.toolbar.fingerprints.DrawerContentViewConstructorFingerprint
import app.revanced.patches.youtube.general.toolbar.fingerprints.DrawerContentViewFingerprint
import app.revanced.patches.youtube.general.toolbar.fingerprints.SearchBarFingerprint import app.revanced.patches.youtube.general.toolbar.fingerprints.SearchBarFingerprint
import app.revanced.patches.youtube.general.toolbar.fingerprints.SearchBarParentFingerprint import app.revanced.patches.youtube.general.toolbar.fingerprints.SearchBarParentFingerprint
import app.revanced.patches.youtube.general.toolbar.fingerprints.SearchResultFingerprint import app.revanced.patches.youtube.general.toolbar.fingerprints.SearchResultFingerprint
@ -24,9 +27,13 @@ import app.revanced.patches.youtube.utils.integrations.Constants.COMPATIBLE_PACK
import app.revanced.patches.youtube.utils.integrations.Constants.GENERAL_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.integrations.Constants.GENERAL_CLASS_DESCRIPTOR
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.VoiceSearch import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.VoiceSearch
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.YtPremiumWordMarkHeader
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.YtWordMarkHeader
import app.revanced.patches.youtube.utils.settings.SettingsPatch import app.revanced.patches.youtube.utils.settings.SettingsPatch
import app.revanced.patches.youtube.utils.settings.SettingsPatch.contexts import app.revanced.patches.youtube.utils.settings.SettingsPatch.contexts
import app.revanced.patches.youtube.utils.toolbar.ToolBarHookPatch import app.revanced.patches.youtube.utils.toolbar.ToolBarHookPatch
import app.revanced.util.findMutableMethodOf
import app.revanced.util.getTargetIndex
import app.revanced.util.getTargetIndexWithMethodReferenceName import app.revanced.util.getTargetIndexWithMethodReferenceName
import app.revanced.util.getTargetIndexWithReference import app.revanced.util.getTargetIndexWithReference
import app.revanced.util.getTargetIndexWithReferenceReversed import app.revanced.util.getTargetIndexWithReferenceReversed
@ -35,10 +42,13 @@ import app.revanced.util.getWideLiteralInstructionIndex
import app.revanced.util.literalInstructionBooleanHook import app.revanced.util.literalInstructionBooleanHook
import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.patch.BaseBytecodePatch
import app.revanced.util.resultOrThrow import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction31i
import com.android.tools.smali.dexlib2.util.MethodUtil
@Suppress("DEPRECATION", "unused") @Suppress("DEPRECATION", "unused")
object ToolBarComponentsPatch : BaseBytecodePatch( object ToolBarComponentsPatch : BaseBytecodePatch(
@ -52,7 +62,9 @@ object ToolBarComponentsPatch : BaseBytecodePatch(
), ),
compatiblePackages = COMPATIBLE_PACKAGE, compatiblePackages = COMPATIBLE_PACKAGE,
fingerprints = setOf( fingerprints = setOf(
AttributeResolverFingerprint,
CreateSearchSuggestionsFingerprint, CreateSearchSuggestionsFingerprint,
DrawerContentViewConstructorFingerprint,
SearchBarParentFingerprint, SearchBarParentFingerprint,
SearchResultFingerprint, SearchResultFingerprint,
SetActionBarRingoFingerprint, SetActionBarRingoFingerprint,
@ -72,10 +84,67 @@ object ToolBarComponentsPatch : BaseBytecodePatch(
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
// region patch for change header
// Invoke YouTube's header attribute into integrations.
replaceHeaderAttributeId(context)
// YouTube's headers have the form of AttributeSet, which is decoded from YouTube's built-in classes.
val attributeResolverMethod = AttributeResolverFingerprint.resultOrThrow().mutableMethod
val attributeResolverMethodCall = attributeResolverMethod.definingClass + "->" + attributeResolverMethod.name + "(Landroid/content/Context;I)Landroid/graphics/drawable/Drawable;"
context.findClass(GENERAL_CLASS_DESCRIPTOR)!!.mutableClass.methods.single { method ->
method.name == "getHeaderDrawable"
}.addInstructions(
0, """
invoke-static {p0, p1}, $attributeResolverMethodCall
move-result-object p0
return-object p0
"""
)
// The sidebar's header is lithoView. Add a listener to change it.
DrawerContentViewFingerprint.resolve(
context,
DrawerContentViewConstructorFingerprint.resultOrThrow().classDef
)
DrawerContentViewFingerprint.resultOrThrow().let {
it.mutableMethod.apply {
val insertIndex = getTargetIndexWithMethodReferenceName("addView")
val insertRegister = getInstruction<FiveRegisterInstruction>(insertIndex).registerD
addInstruction(
insertIndex,
"invoke-static {v$insertRegister}, $GENERAL_CLASS_DESCRIPTOR->setDrawerNavigationHeader(Landroid/view/View;)V"
)
}
}
// Override the header in the search bar.
val setActionBarRingoMutableClass = SetActionBarRingoFingerprint.resultOrThrow().mutableClass
setActionBarRingoMutableClass.methods.first {
method -> MethodUtil.isConstructor(method)
}.apply {
val insertIndex = getTargetIndex(Opcode.IPUT_BOOLEAN)
val insertRegister = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
addInstruction(
insertIndex + 1,
"const/4 v$insertRegister, 0x0"
)
addInstructions(
insertIndex, """
invoke-static {}, $GENERAL_CLASS_DESCRIPTOR->overridePremiumHeader()Z
move-result v$insertRegister
"""
)
}
// endregion
// region patch for enable wide search bar // region patch for enable wide search bar
val parentClassDef = SetActionBarRingoFingerprint.resultOrThrow().classDef YouActionBarFingerprint.resolve(context, setActionBarRingoMutableClass)
YouActionBarFingerprint.resolve(context, parentClassDef)
SetWordMarkHeaderFingerprint.resultOrThrow().let { SetWordMarkHeaderFingerprint.resultOrThrow().let {
val walkerMethod = it.getWalkerMethod(context, it.scanResult.patternScanResult!!.startIndex + 1) val walkerMethod = it.getWalkerMethod(context, it.scanResult.patternScanResult!!.startIndex + 1)
@ -243,6 +312,36 @@ object ToolBarComponentsPatch : BaseBytecodePatch(
SettingsPatch.updatePatchStatus(this) SettingsPatch.updatePatchStatus(this)
} }
private fun replaceHeaderAttributeId(context: BytecodeContext) {
val headerAttributeIdArray = arrayOf(YtPremiumWordMarkHeader, YtWordMarkHeader)
context.classes.forEach { classDef ->
classDef.methods.forEach { method ->
method.implementation.apply {
this?.instructions?.forEachIndexed { index, instruction ->
if (instruction.opcode != Opcode.CONST)
return@forEachIndexed
if (headerAttributeIdArray.indexOf((instruction as Instruction31i).wideLiteral) < 0)
return@forEachIndexed
(instructions.elementAt(index)).apply {
val register = (this as OneRegisterInstruction).registerA
context.proxy(classDef)
.mutableClass
.findMutableMethodOf(method)
.addInstructions(
index + 1, """
invoke-static {}, $GENERAL_CLASS_DESCRIPTOR->getHeaderAttributeId()I
move-result v$register
"""
)
}
}
}
}
}
}
private fun MutableMethod.injectSearchBarHook( private fun MutableMethod.injectSearchBarHook(
insertIndex: Int, insertIndex: Int,
descriptor: String descriptor: String

View File

@ -0,0 +1,12 @@
package app.revanced.patches.youtube.general.toolbar.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal object AttributeResolverFingerprint : MethodFingerprint(
returnType = "Landroid/graphics/drawable/Drawable;",
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
parameters = listOf("Landroid/content/Context;", "I"),
strings = listOf("Type of attribute is not a reference to a drawable (attr = %d, value = %s)")
)

View File

@ -0,0 +1,11 @@
package app.revanced.patches.youtube.general.toolbar.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.DrawerContentView
import app.revanced.util.fingerprint.LiteralValueFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal object DrawerContentViewConstructorFingerprint : LiteralValueFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
literalSupplier = { DrawerContentView }
)

View File

@ -0,0 +1,16 @@
package app.revanced.patches.youtube.general.toolbar.fingerprints
import app.revanced.util.fingerprint.MethodReferenceNameFingerprint
import com.android.tools.smali.dexlib2.Opcode
internal object DrawerContentViewFingerprint : MethodReferenceNameFingerprint(
returnType = "V",
parameters = listOf("L"),
opcodes = listOf(
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.NEW_INSTANCE,
Opcode.INVOKE_DIRECT,
),
reference = { "addView" }
)

View File

@ -1,69 +0,0 @@
package app.revanced.patches.youtube.layout.header
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption
import app.revanced.patches.youtube.utils.integrations.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.settings.ResourceUtils.updatePatchStatusHeader
import app.revanced.patches.youtube.utils.settings.SettingsPatch
import app.revanced.util.patch.BaseResourcePatch
import kotlin.io.path.copyTo
@Suppress("DEPRECATION", "unused")
object PremiumHeadingPatch : BaseResourcePatch(
name = "Premium heading",
description = "Show or hide the premium heading.",
dependencies = setOf(SettingsPatch::class),
compatiblePackages = COMPATIBLE_PACKAGE,
use = false
) {
private const val DEFAULT_HEADING_RES = "yt_wordmark_header"
private const val PREMIUM_HEADING_RES = "yt_premium_wordmark_header"
private val UsePremiumHeading by booleanPatchOption(
key = "UsePremiumHeading",
default = true,
title = "Use premium heading",
description = "Whether to use the premium heading.",
required = true
)
override fun execute(context: ResourceContext) {
val resDirectory = context["res"]
val (original, replacement) = if (UsePremiumHeading == true)
PREMIUM_HEADING_RES to DEFAULT_HEADING_RES
else
DEFAULT_HEADING_RES to PREMIUM_HEADING_RES
val variants = arrayOf("light", "dark")
arrayOf(
"xxxhdpi",
"xxhdpi",
"xhdpi",
"hdpi",
"mdpi"
).mapNotNull { dpi ->
resDirectory.resolve("drawable-$dpi").takeIf { it.exists() }?.toPath()
}.also {
if (it.isEmpty())
throw PatchException("The drawable folder can not be found. Therefore, the patch can not be applied.")
}.forEach { path ->
variants.forEach { mode ->
val fromPath = path.resolve("${original}_$mode.png")
val toPath = path.resolve("${replacement}_$mode.png")
fromPath.copyTo(toPath, true)
}
}
val header = if (UsePremiumHeading == true)
"Premium"
else
"Default"
context.updatePatchStatusHeader(header)
}
}

View File

@ -5,6 +5,7 @@ import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.shared.mapping.ResourceMappingPatch import app.revanced.patches.shared.mapping.ResourceMappingPatch
import app.revanced.patches.shared.mapping.ResourceMappingPatch.getId import app.revanced.patches.shared.mapping.ResourceMappingPatch.getId
import app.revanced.patches.shared.mapping.ResourceType.ATTR
import app.revanced.patches.shared.mapping.ResourceType.COLOR import app.revanced.patches.shared.mapping.ResourceType.COLOR
import app.revanced.patches.shared.mapping.ResourceType.DIMEN import app.revanced.patches.shared.mapping.ResourceType.DIMEN
import app.revanced.patches.shared.mapping.ResourceType.DRAWABLE import app.revanced.patches.shared.mapping.ResourceType.DRAWABLE
@ -37,6 +38,7 @@ object SharedResourceIdPatch : ResourcePatch() {
var ControlsLayoutStub = -1L var ControlsLayoutStub = -1L
var DarkSplashAnimation = -1L var DarkSplashAnimation = -1L
var DonationCompanion = -1L var DonationCompanion = -1L
var DrawerContentView = -1L
var DrawerResults = -1L var DrawerResults = -1L
var EasySeekEduContainer = -1L var EasySeekEduContainer = -1L
var EditSettingsAction = -1L var EditSettingsAction = -1L
@ -87,6 +89,8 @@ object SharedResourceIdPatch : ResourcePatch() {
var VideoQualityBottomSheet = -1L var VideoQualityBottomSheet = -1L
var VoiceSearch = -1L var VoiceSearch = -1L
var YouTubeControlsOverlaySubtitleButton = -1L var YouTubeControlsOverlaySubtitleButton = -1L
var YtPremiumWordMarkHeader = -1L
var YtWordMarkHeader = -1L
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
@ -111,6 +115,7 @@ object SharedResourceIdPatch : ResourcePatch() {
ControlsLayoutStub = getId(ID, "controls_layout_stub") ControlsLayoutStub = getId(ID, "controls_layout_stub")
DarkSplashAnimation = getId(ID, "dark_splash_animation") DarkSplashAnimation = getId(ID, "dark_splash_animation")
DonationCompanion = getId(LAYOUT, "donation_companion") DonationCompanion = getId(LAYOUT, "donation_companion")
DrawerContentView = getId(ID, "drawer_content_view")
DrawerResults = getId(ID, "drawer_results") DrawerResults = getId(ID, "drawer_results")
EasySeekEduContainer = getId(ID, "easy_seek_edu_container") EasySeekEduContainer = getId(ID, "easy_seek_edu_container")
EditSettingsAction = getId(STRING, "edit_settings_action") EditSettingsAction = getId(STRING, "edit_settings_action")
@ -163,6 +168,8 @@ object SharedResourceIdPatch : ResourcePatch() {
VideoQualityBottomSheet = getId(LAYOUT, "video_quality_bottom_sheet_list_fragment_title") VideoQualityBottomSheet = getId(LAYOUT, "video_quality_bottom_sheet_list_fragment_title")
VoiceSearch = getId(ID, "voice_search") VoiceSearch = getId(ID, "voice_search")
YouTubeControlsOverlaySubtitleButton = getId(LAYOUT, "youtube_controls_overlay_subtitle_button") YouTubeControlsOverlaySubtitleButton = getId(LAYOUT, "youtube_controls_overlay_subtitle_button")
YtPremiumWordMarkHeader = getId(ATTR, "ytPremiumWordmarkHeader")
YtWordMarkHeader = getId(ATTR, "ytWordmarkHeader")
} }
} }

View File

@ -71,10 +71,6 @@ object ResourceUtils {
updatePatchStatusSettings(patchTitle, "@string/revanced_patches_included") updatePatchStatusSettings(patchTitle, "@string/revanced_patches_included")
} }
fun ResourceContext.updatePatchStatusHeader(headerName: String) {
updatePatchStatusSettings("Header", headerName)
}
fun ResourceContext.updatePatchStatusIcon(iconName: String) { fun ResourceContext.updatePatchStatusIcon(iconName: String) {
updatePatchStatusSettings("Icon", "@string/revanced_icon_$iconName") updatePatchStatusSettings("Icon", "@string/revanced_icon_$iconName")
} }

View File

@ -370,6 +370,9 @@ Some components may not be hidden."</string>
<string name="revanced_preference_screen_toolbar_title">Toolbar</string> <string name="revanced_preference_screen_toolbar_title">Toolbar</string>
<string name="revanced_preference_screen_toolbar_summary">Hide or change components located on the toolbar such as toolbar buttons, search bar, header.</string> <string name="revanced_preference_screen_toolbar_summary">Hide or change components located on the toolbar such as toolbar buttons, search bar, header.</string>
<string name="revanced_change_youtube_header_title">Change YouTube header</string>
<string name="revanced_change_youtube_header_summary_on">Premium header is enabled.</string>
<string name="revanced_change_youtube_header_summary_off">Generic headers is enabled.</string>
<string name="revanced_enable_wide_search_bar_title">Enable wide search bar</string> <string name="revanced_enable_wide_search_bar_title">Enable wide search bar</string>
<string name="revanced_enable_wide_search_bar_summary_on">Wide search bar is enabled.</string> <string name="revanced_enable_wide_search_bar_summary_on">Wide search bar is enabled.</string>
<string name="revanced_enable_wide_search_bar_summary_off">Wide search bar is disabled.</string> <string name="revanced_enable_wide_search_bar_summary_off">Wide search bar is disabled.</string>
@ -1352,7 +1355,6 @@ Limitation: Feed videos will play for less than 1 minute before encountering pla
<!-- PreferenceScreen: Miscellaneous, PreferenceCategory: Miscellaneous, PreferenceScreen: Patch information, PreferenceCategory: Others --> <!-- PreferenceScreen: Miscellaneous, PreferenceCategory: Miscellaneous, PreferenceScreen: Patch information, PreferenceCategory: Others -->
<string name="revanced_preference_category_others">Others</string> <string name="revanced_preference_category_others">Others</string>
<string name="revanced_header_default">Stock</string>
<string name="revanced_icon_custom">Custom</string> <string name="revanced_icon_custom">Custom</string>
<string name="revanced_icon_default">Stock</string> <string name="revanced_icon_default">Stock</string>
<string name="revanced_icon_mmt">MMT</string> <string name="revanced_icon_mmt">MMT</string>

View File

@ -128,6 +128,7 @@
<!-- SETTINGS: TOOLBAR_COMPONENTS <!-- SETTINGS: TOOLBAR_COMPONENTS
<PreferenceScreen android:title="@string/revanced_preference_screen_toolbar_title" android:key="revanced_preference_screen_toolbar" android:summary="@string/revanced_preference_screen_toolbar_summary"> <PreferenceScreen android:title="@string/revanced_preference_screen_toolbar_title" android:key="revanced_preference_screen_toolbar" android:summary="@string/revanced_preference_screen_toolbar_summary">
<SwitchPreference android:title="@string/revanced_change_youtube_header_title" android:key="revanced_change_youtube_header" android:defaultValue="true" android:summaryOn="@string/revanced_change_youtube_header_summary_on" android:summaryOff="@string/revanced_change_youtube_header_summary_off" />
<SwitchPreference android:title="@string/revanced_enable_wide_search_bar_title" android:key="revanced_enable_wide_search_bar" android:defaultValue="false" android:summaryOn="@string/revanced_enable_wide_search_bar_summary_on" android:summaryOff="@string/revanced_enable_wide_search_bar_summary_off" /> <SwitchPreference android:title="@string/revanced_enable_wide_search_bar_title" android:key="revanced_enable_wide_search_bar" android:defaultValue="false" android:summaryOn="@string/revanced_enable_wide_search_bar_summary_on" android:summaryOff="@string/revanced_enable_wide_search_bar_summary_off" />
<SwitchPreference android:title="@string/revanced_enable_wide_search_bar_in_you_tab_title" android:key="revanced_enable_wide_search_bar_in_you_tab" android:defaultValue="false" android:summary="@string/revanced_enable_wide_search_bar_in_you_tab_summary" android:dependency="revanced_enable_wide_search_bar" /> <SwitchPreference android:title="@string/revanced_enable_wide_search_bar_in_you_tab_title" android:key="revanced_enable_wide_search_bar_in_you_tab" android:defaultValue="false" android:summary="@string/revanced_enable_wide_search_bar_in_you_tab_summary" android:dependency="revanced_enable_wide_search_bar" />
<SwitchPreference android:title="@string/revanced_hide_toolbar_cast_button_title" android:key="revanced_hide_toolbar_cast_button" android:defaultValue="true" android:summaryOn="@string/revanced_hide_toolbar_cast_button_summary_on" android:summaryOff="@string/revanced_hide_toolbar_cast_button_summary_off" /> <SwitchPreference android:title="@string/revanced_hide_toolbar_cast_button_title" android:key="revanced_hide_toolbar_cast_button" android:defaultValue="true" android:summaryOn="@string/revanced_hide_toolbar_cast_button_summary_on" android:summaryOff="@string/revanced_hide_toolbar_cast_button_summary_off" />
@ -606,7 +607,6 @@
<PreferenceCategory android:title="@string/revanced_preference_category_others" android:layout="@layout/revanced_settings_preferences_category"> <PreferenceCategory android:title="@string/revanced_preference_category_others" android:layout="@layout/revanced_settings_preferences_category">
<Preference android:title="Custom double tap length" android:summary="@string/revanced_patches_excluded" android:selectable="false"/> <Preference android:title="Custom double tap length" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
<Preference android:title="Disable pip notification" android:summary="@string/revanced_patches_excluded" android:selectable="false"/> <Preference android:title="Disable pip notification" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
<Preference android:title="Header" android:summary="@string/revanced_header_default" android:selectable="false"/>
<Preference android:title="Hide animated button background" android:summary="@string/revanced_patches_excluded" android:selectable="false"/> <Preference android:title="Hide animated button background" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
<Preference android:title="Hide double tap overlay filter" android:summary="@string/revanced_patches_excluded" android:selectable="false"/> <Preference android:title="Hide double tap overlay filter" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
<Preference android:title="Hide tooltip content" android:summary="@string/revanced_patches_excluded" android:selectable="false"/> <Preference android:title="Hide tooltip content" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>