feat(YouTube Music): Support patching on Android 5-7 (#140)

* feat(YouTube Music): Support patching on Android 5-7

* Improve comments
This commit is contained in:
kitadai31 2025-02-12 09:55:37 +09:00 committed by GitHub
parent 325b43e394
commit 967776a484
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 78 additions and 40 deletions

View File

@ -11,6 +11,7 @@ import app.revanced.patches.music.utils.playservice.versionCheckPatch
import app.revanced.patches.music.utils.settings.ResourceUtils.setIconType import app.revanced.patches.music.utils.settings.ResourceUtils.setIconType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.settingsPatch import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.util.FilesCompat
import app.revanced.util.ResourceGroup import app.revanced.util.ResourceGroup
import app.revanced.util.Utils.trimIndentMultiline import app.revanced.util.Utils.trimIndentMultiline
import app.revanced.util.copyAdaptiveIcon import app.revanced.util.copyAdaptiveIcon
@ -20,7 +21,6 @@ import app.revanced.util.underBarOrThrow
import app.revanced.util.valueOrThrow import app.revanced.util.valueOrThrow
import org.w3c.dom.Element import org.w3c.dom.Element
import java.io.File import java.io.File
import java.nio.file.Files
private const val ADAPTIVE_ICON_BACKGROUND_FILE_NAME = private const val ADAPTIVE_ICON_BACKGROUND_FILE_NAME =
"adaptiveproduct_youtube_music_background_color_108" "adaptiveproduct_youtube_music_background_color_108"
@ -155,9 +155,9 @@ val customBrandingIconPatch = resourcePatch(
val toDirectory = resourceDirectory.resolve(group.resourceDirectoryName) val toDirectory = resourceDirectory.resolve(group.resourceDirectoryName)
group.resources.forEach { iconFileName -> group.resources.forEach { iconFileName ->
Files.write( FilesCompat.copy(
toDirectory.resolve(iconFileName).toPath(), fromDirectory.resolve(iconFileName),
fromDirectory.resolve(iconFileName).readBytes() toDirectory.resolve(iconFileName)
) )
} }
} }

View File

@ -1,8 +1,8 @@
package app.revanced.patches.shared.materialyou package app.revanced.patches.shared.materialyou
import app.revanced.patcher.patch.ResourcePatchContext import app.revanced.patcher.patch.ResourcePatchContext
import app.revanced.util.FilesCompat
import org.w3c.dom.Element import org.w3c.dom.Element
import java.nio.file.Files
private fun ResourcePatchContext.patchXmlFile( private fun ResourcePatchContext.patchXmlFile(
fromDir: String, fromDir: String,
@ -17,7 +17,7 @@ private fun ResourcePatchContext.patchXmlFile(
val fromDirectory = resourceDirectory.resolve(fromDir) val fromDirectory = resourceDirectory.resolve(fromDir)
val toDirectory = resourceDirectory.resolve(toDir) val toDirectory = resourceDirectory.resolve(toDir)
if (!toDirectory.isDirectory) Files.createDirectories(toDirectory.toPath()) if (!toDirectory.isDirectory) toDirectory.mkdirs()
val fromXmlFile = fromDirectory.resolve(xmlFileName) val fromXmlFile = fromDirectory.resolve(xmlFileName)
val toXmlFile = toDirectory.resolve(xmlFileName) val toXmlFile = toDirectory.resolve(xmlFileName)
@ -27,9 +27,9 @@ private fun ResourcePatchContext.patchXmlFile(
} }
if (!toXmlFile.exists()) { if (!toXmlFile.exists()) {
Files.copy( FilesCompat.copy(
fromXmlFile.toPath(), fromXmlFile,
toXmlFile.toPath() toXmlFile
) )
} }

View File

@ -2,13 +2,12 @@ package app.revanced.patches.shared.translations
import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.ResourcePatchContext import app.revanced.patcher.patch.ResourcePatchContext
import app.revanced.util.FilesCompat
import app.revanced.util.doRecursively import app.revanced.util.doRecursively
import app.revanced.util.inputStreamFromBundledResource import app.revanced.util.inputStreamFromBundledResource
import org.w3c.dom.Element import org.w3c.dom.Element
import org.w3c.dom.Node import org.w3c.dom.Node
import java.io.File import java.io.File
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import javax.xml.parsers.DocumentBuilderFactory import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerFactory import javax.xml.transform.TransformerFactory
@ -173,12 +172,11 @@ private fun ResourcePatchContext.copyStringsXml(
)?.let { inputStream -> )?.let { inputStream ->
val directory = "values-$language-v21" val directory = "values-$language-v21"
val valuesV21Directory = resourceDirectory.resolve(directory) val valuesV21Directory = resourceDirectory.resolve(directory)
if (!valuesV21Directory.isDirectory) Files.createDirectories(valuesV21Directory.toPath()) if (!valuesV21Directory.isDirectory) valuesV21Directory.mkdirs()
Files.copy( FilesCompat.copy(
inputStream, inputStream,
resourceDirectory.resolve("$directory/strings.xml").toPath(), resourceDirectory.resolve("$directory/strings.xml")
StandardCopyOption.REPLACE_EXISTING
) )
} }
} }

View File

@ -0,0 +1,53 @@
package app.revanced.util
import java.io.File
import java.io.InputStream
import java.nio.file.Files
import java.nio.file.StandardCopyOption
/**
* Provides java.nio.file.Files compatible functions.
*
* This is needed for the ReVanced Manager running on Android 5.0-7.1
* because Android 7.1 and below does not support the Java NIO2 Files API.
*/
internal object FilesCompat {
private val useCompat = try {
// Check for the existence of java.nio.file.Files class
Class.forName("java.nio.file.Files")
false
} catch (_ : ClassNotFoundException) {
// Under Android 8.0
true
}
/**
* Copy a file to a target file.
*
* If the `target` file already exists, replace an existing file.
*/
fun copy(source: File, target: File) {
if (useCompat) {
source.copyTo(target, overwrite = true)
} else {
Files.copy(source.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
}
/**
* Copies all bytes from an input stream to a file.
*
* If the `target` file already exists, replace an existing file.
*/
fun copy(source: InputStream, target: File) {
if (useCompat) {
source.use { inputStream ->
target.outputStream().use { outputStream ->
inputStream.copyTo(outputStream)
}
}
} else {
Files.copy(source, target.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
}
}

View File

@ -11,8 +11,6 @@ import org.w3c.dom.Node
import org.w3c.dom.NodeList import org.w3c.dom.NodeList
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.nio.file.Files
import java.nio.file.StandardCopyOption
private val classLoader = object {}.javaClass.classLoader private val classLoader = object {}.javaClass.classLoader
@ -115,14 +113,9 @@ fun ResourcePatchContext.copyAdaptiveIcon(
if (oldIconResourceFile != newIconResourceFile) { if (oldIconResourceFile != newIconResourceFile) {
mipmapDirectories.forEach { mipmapDirectories.forEach {
val mipmapDirectory = get("res").resolve(it) val mipmapDirectory = get("res").resolve(it)
Files.copy( FilesCompat.copy(
mipmapDirectory mipmapDirectory.resolve("$oldIconResourceFile.png"),
.resolve("$oldIconResourceFile.png") mipmapDirectory.resolve("$newIconResourceFile.png")
.toPath(),
mipmapDirectory
.resolve("$newIconResourceFile.png")
.toPath(),
StandardCopyOption.REPLACE_EXISTING
) )
} }
} }
@ -132,14 +125,9 @@ fun ResourcePatchContext.copyAdaptiveIcon(
adaptiveIconMonoChromeFileName != getAdaptiveIconMonoChromeResourceFile() adaptiveIconMonoChromeFileName != getAdaptiveIconMonoChromeResourceFile()
) { ) {
val drawableDirectory = get("res").resolve("drawable") val drawableDirectory = get("res").resolve("drawable")
Files.copy( FilesCompat.copy(
drawableDirectory drawableDirectory.resolve("$adaptiveIconMonoChromeFileName.xml"),
.resolve("$adaptiveIconMonoChromeFileName.xml") drawableDirectory.resolve("${getAdaptiveIconMonoChromeResourceFile()}.xml")
.toPath(),
drawableDirectory
.resolve("${getAdaptiveIconMonoChromeResourceFile()}.xml")
.toPath(),
StandardCopyOption.REPLACE_EXISTING
) )
} }
} }
@ -201,9 +189,9 @@ fun ResourcePatchContext.copyFile(
val toDirectory = resourceDirectory.resolve(group.resourceDirectoryName) val toDirectory = resourceDirectory.resolve(group.resourceDirectoryName)
group.resources.forEach { iconFileName -> group.resources.forEach { iconFileName ->
Files.write( FilesCompat.copy(
toDirectory.resolve(iconFileName).toPath(), fromDirectory.resolve(iconFileName),
fromDirectory.resolve(iconFileName).readBytes() toDirectory.resolve(iconFileName)
) )
} }
} }
@ -307,16 +295,15 @@ fun ResourcePatchContext.copyResources(
resourceGroup.resources.forEach { resource -> resourceGroup.resources.forEach { resource ->
val resourceDirectoryName = resourceGroup.resourceDirectoryName val resourceDirectoryName = resourceGroup.resourceDirectoryName
val targetDirectory = resourceDirectory.resolve(resourceDirectoryName) val targetDirectory = resourceDirectory.resolve(resourceDirectoryName)
if (!targetDirectory.isDirectory) Files.createDirectories(targetDirectory.toPath()) if (!targetDirectory.isDirectory) targetDirectory.mkdirs()
val resourceFile = "$resourceDirectoryName/$resource" val resourceFile = "$resourceDirectoryName/$resource"
inputStreamFromBundledResource( inputStreamFromBundledResource(
sourceResourceDirectory, sourceResourceDirectory,
resourceFile resourceFile
)?.let { inputStream -> )?.let { inputStream ->
Files.copy( FilesCompat.copy(
inputStream, inputStream,
resourceDirectory.resolve(resourceFile).toPath(), resourceDirectory.resolve(resourceFile),
StandardCopyOption.REPLACE_EXISTING,
) )
} }
} }