feat(YouTube Music): add Enable next previous button patch

This commit is contained in:
inotia00
2024-03-23 19:04:57 +09:00
parent 3fe386be81
commit ab1447853d
7 changed files with 345 additions and 0 deletions

View File

@ -0,0 +1,176 @@
package app.revanced.patches.music.player.nextprevious
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.music.utils.fingerprints.MiniPlayerConstructorFingerprint
import app.revanced.patches.music.player.nextprevious.fingerprints.MiniPlayerParentFingerprint
import app.revanced.patches.music.player.nextprevious.fingerprints.MppWatchWhileLayoutFingerprint
import app.revanced.patches.music.player.nextprevious.fingerprints.NextButtonVisibilityFingerprint
import app.revanced.patches.music.utils.integrations.Constants.PLAYER
import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch
import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.MiniPlayerPlayPauseReplayButton
import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.TopEnd
import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.TopStart
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.SettingsPatch
import app.revanced.util.exception
import app.revanced.util.getTargetIndex
import app.revanced.util.getWideLiteralInstructionIndex
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Patch(
name = "Enable next previous button",
description = "Adds an options to show the next and previous buttons to the miniplayer.",
dependencies = [
MiniPlayerButtonResourcePatch::class,
SettingsPatch::class,
SharedResourceIdPatch::class
],
compatiblePackages = [
CompatiblePackage(
"com.google.android.apps.youtube.music",
[
"6.21.52",
"6.22.52",
"6.23.56",
"6.25.53",
"6.26.51",
"6.27.54",
"6.28.53",
"6.29.58",
"6.31.55",
"6.33.52"
]
)
]
)
@Suppress("unused")
object MiniPlayerButtonPatch : BytecodePatch(
setOf(
MiniPlayerConstructorFingerprint,
MiniPlayerParentFingerprint,
MppWatchWhileLayoutFingerprint
)
) {
private const val NEXT_BUTTON_FIELD_NAME =
"nextButton"
private const val PREVIOUS_BUTTON_FIELD_NAME =
"previousButton"
private const val NEXT_BUTTON_METHOD_NAME =
"setNextButtonOnClickListener"
private const val PREVIOUS_BUTTON_METHOD_NAME =
"setPreviousButtonOnClickListener"
private var arrayCount = 1
override fun execute(context: BytecodeContext) {
val miniPlayerConstructorMutableMethod =
MiniPlayerConstructorFingerprint.result?.mutableMethod
?: throw MiniPlayerConstructorFingerprint.exception
val mppWatchWhileLayoutMutableMethod =
MppWatchWhileLayoutFingerprint.result?.mutableMethod
?: throw MppWatchWhileLayoutFingerprint.exception
if (!SettingsPatch.upward0642) {
MiniPlayerParentFingerprint.result?.let { parentResult ->
// Resolves fingerprints
NextButtonVisibilityFingerprint.resolve(context, parentResult.classDef)
NextButtonVisibilityFingerprint.result?.let {
it.mutableMethod.apply {
val targetIndex = it.scanResult.patternScanResult!!.startIndex + 1
val targetRegister =
getInstruction<OneRegisterInstruction>(targetIndex).registerA
addInstructions(
targetIndex + 1, """
invoke-static {v$targetRegister}, $PLAYER->enableMiniPlayerNextButton(Z)Z
move-result v$targetRegister
"""
)
}
} ?: throw NextButtonVisibilityFingerprint.exception
} ?: throw MiniPlayerParentFingerprint.exception
} else {
miniPlayerConstructorMutableMethod.setInstanceFieldValue(NEXT_BUTTON_METHOD_NAME, TopStart)
mppWatchWhileLayoutMutableMethod.setStaticFieldValue(NEXT_BUTTON_FIELD_NAME, TopStart)
}
miniPlayerConstructorMutableMethod.setInstanceFieldValue(PREVIOUS_BUTTON_METHOD_NAME, TopEnd)
mppWatchWhileLayoutMutableMethod.setStaticFieldValue(PREVIOUS_BUTTON_FIELD_NAME, TopEnd)
mppWatchWhileLayoutMutableMethod.setViewArray()
SettingsPatch.addMusicPreference(
CategoryType.PLAYER,
"revanced_enable_mini_player_next_button",
"true"
)
SettingsPatch.addMusicPreference(
CategoryType.PLAYER,
"revanced_enable_mini_player_previous_button",
"false"
)
}
private fun MutableMethod.setViewArray() {
val miniPlayerPlayPauseReplayButtonIndex = getWideLiteralInstructionIndex(MiniPlayerPlayPauseReplayButton)
val invokeStaticIndex = getTargetIndex(miniPlayerPlayPauseReplayButtonIndex, Opcode.INVOKE_STATIC)
val viewArrayRegister = getInstruction<FiveRegisterInstruction>(invokeStaticIndex).registerC
addInstructions(
invokeStaticIndex, """
invoke-static {v$viewArrayRegister}, $PLAYER->getViewArray([Landroid/view/View;)[Landroid/view/View;
move-result-object v$viewArrayRegister
"""
)
}
private fun MutableMethod.setInstanceFieldValue(
methodName: String,
viewId: Long
) {
val miniPlayerPlayPauseReplayButtonIndex = getWideLiteralInstructionIndex(MiniPlayerPlayPauseReplayButton)
val miniPlayerPlayPauseReplayButtonRegister = getInstruction<OneRegisterInstruction>(miniPlayerPlayPauseReplayButtonIndex).registerA
val findViewByIdIndex = getTargetIndex(miniPlayerPlayPauseReplayButtonIndex, Opcode.INVOKE_VIRTUAL)
val parentViewRegister = getInstruction<FiveRegisterInstruction>(findViewByIdIndex).registerC
addInstructions(
miniPlayerPlayPauseReplayButtonIndex, """
const v$miniPlayerPlayPauseReplayButtonRegister, $viewId
invoke-virtual {v$parentViewRegister, v$miniPlayerPlayPauseReplayButtonRegister}, Landroid/view/View;->findViewById(I)Landroid/view/View;
move-result-object v$miniPlayerPlayPauseReplayButtonRegister
invoke-static {v$miniPlayerPlayPauseReplayButtonRegister}, $PLAYER->$methodName(Landroid/view/View;)V
"""
)
}
private fun MutableMethod.setStaticFieldValue(
fieldName: String,
viewId: Long
) {
val miniPlayerPlayPauseReplayButtonIndex = getWideLiteralInstructionIndex(MiniPlayerPlayPauseReplayButton)
val constRegister = getInstruction<OneRegisterInstruction>(miniPlayerPlayPauseReplayButtonIndex).registerA
val findViewByIdIndex = getTargetIndex(miniPlayerPlayPauseReplayButtonIndex, Opcode.INVOKE_VIRTUAL)
val findViewByIdRegister = getInstruction<FiveRegisterInstruction>(findViewByIdIndex).registerC
addInstructions(
miniPlayerPlayPauseReplayButtonIndex, """
const v$constRegister, $viewId
invoke-virtual {v$findViewByIdRegister, v$constRegister}, $definingClass->findViewById(I)Landroid/view/View;
move-result-object v$constRegister
sput-object v$constRegister, $PLAYER->$fieldName:Landroid/view/View;
"""
)
}
}

View File

@ -0,0 +1,114 @@
package app.revanced.patches.music.player.nextprevious
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.DomFileEditor
import app.revanced.patches.music.utils.settings.SettingsPatch
import app.revanced.util.adoptChild
import app.revanced.util.insertNode
import app.revanced.util.doRecursively
import org.w3c.dom.Element
import java.io.Closeable
@Patch(dependencies = [SettingsPatch::class])
object MiniPlayerButtonResourcePatch : ResourcePatch(), Closeable {
private const val IMAGE_VIEW_TAG_NAME =
"com.google.android.libraries.youtube.common.ui.TouchImageView"
private const val NEXT_BUTTON_VIEW_ID =
"mini_player_next_button"
private const val PREVIOUS_BUTTON_VIEW_ID =
"mini_player_previous_button"
private lateinit var targetXmlEditor: DomFileEditor
private var shouldAddPreviousButton = true
override fun execute(context: ResourceContext) {
targetXmlEditor = context.xmlEditor["res/layout/watch_while_layout.xml"]
// Since YT Music v6.42.51,the resources for the next button have been removed, we need to add them manually.
if (SettingsPatch.upward0642) {
context.replaceId(false)
insertNode(false)
}
context.replaceId(true)
insertNode(true)
}
override fun close() = targetXmlEditor.close()
private fun insertNode(isPreviousButton: Boolean) {
targetXmlEditor.file.doRecursively loop@{ node ->
if (node !is Element) return@loop
node.getAttributeNode("android:id")?.let { attribute ->
if (isPreviousButton) {
if (attribute.textContent == "@id/mini_player_play_pause_replay_button" && shouldAddPreviousButton) {
node.insertNode(IMAGE_VIEW_TAG_NAME, node) {
setPreviousButtonNodeAttribute()
}
}
} else {
if (attribute.textContent == "@id/mini_player") {
node.adoptChild(IMAGE_VIEW_TAG_NAME) {
setNextButtonNodeAttribute()
}
}
}
}
}
}
private fun ResourceContext.replaceId(isPreviousButton: Boolean) {
val publicFile = this["res/values/public.xml"]
if (isPreviousButton) {
publicFile.writeText(
publicFile.readText()
.replace(
"\"TOP_END\"",
"\"$PREVIOUS_BUTTON_VIEW_ID\""
)
)
} else {
publicFile.writeText(
publicFile.readText()
.replace(
"\"TOP_START\"",
"\"$NEXT_BUTTON_VIEW_ID\""
)
)
}
}
private fun Element.setNextButtonNodeAttribute() {
mapOf(
"android:id" to "@id/$NEXT_BUTTON_VIEW_ID",
"android:padding" to "@dimen/item_medium_spacing",
"android:layout_width" to "@dimen/remix_generic_button_size",
"android:layout_height" to "@dimen/remix_generic_button_size",
"android:src" to "@drawable/music_player_next",
"android:scaleType" to "fitCenter",
"android:contentDescription" to "@string/accessibility_next",
"style" to "@style/MusicPlayerButton"
).forEach { (k, v) ->
setAttribute(k, v)
}
}
private fun Element.setPreviousButtonNodeAttribute() {
mapOf(
"android:id" to "@id/$PREVIOUS_BUTTON_VIEW_ID",
"android:padding" to "@dimen/item_medium_spacing",
"android:layout_width" to "@dimen/remix_generic_button_size",
"android:layout_height" to "@dimen/remix_generic_button_size",
"android:src" to "@drawable/music_player_prev",
"android:scaleType" to "fitCenter",
"android:contentDescription" to "@string/accessibility_previous",
"style" to "@style/MusicPlayerButton"
).forEach { (k, v) ->
setAttribute(k, v)
}
shouldAddPreviousButton = false
}
}

View File

@ -0,0 +1,13 @@
package app.revanced.patches.music.player.nextprevious.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.MiniPlayerMdxPlaying
import app.revanced.util.fingerprint.LiteralValueFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
object MiniPlayerParentFingerprint : LiteralValueFingerprint(
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
literalSupplier = { MiniPlayerMdxPlaying }
)

View File

@ -0,0 +1,16 @@
package app.revanced.patches.music.player.nextprevious.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.MiniPlayerPlayPauseReplayButton
import app.revanced.util.containsWideLiteralInstructionIndex
import com.android.tools.smali.dexlib2.Opcode
object MppWatchWhileLayoutFingerprint : MethodFingerprint(
returnType = "V",
opcodes = listOf(Opcode.NEW_ARRAY),
customFingerprint = { methodDef, _ ->
methodDef.definingClass.endsWith("/MppWatchWhileLayout;")
&& methodDef.name == "onFinishInflate"
&& methodDef.containsWideLiteralInstructionIndex(MiniPlayerPlayPauseReplayButton)
}
)

View File

@ -0,0 +1,18 @@
package app.revanced.patches.music.player.nextprevious.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
object NextButtonVisibilityFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
opcodes = listOf(
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.CONST_16,
Opcode.IF_EQZ
)
)

View File

@ -43,6 +43,8 @@ object SharedResourceIdPatch : ResourcePatch() {
var RemixGenericButtonSize: Long = -1
var Text1: Long = -1
var ToolTipContentView: Long = -1
var TopEnd: Long = -1
var TopStart: Long = -1
var TosFooter: Long = -1
var TouchOutside: Long = -1
@ -81,6 +83,8 @@ object SharedResourceIdPatch : ResourcePatch() {
RemixGenericButtonSize = find(DIMEN, "remix_generic_button_size")
Text1 = find(ID, "text1")
ToolTipContentView = find(LAYOUT, "tooltip_content_view")
TopEnd = find(ID, "TOP_END")
TopStart = find(ID, "TOP_START")
TosFooter = find(ID, "tos_footer")
TouchOutside = find(ID, "touch_outside")