diff --git a/app/src/test/java/io/nekohasekai/sagernet/ui/ShortcutTargetPackageTest.kt b/app/src/test/java/io/nekohasekai/sagernet/ui/ShortcutTargetPackageTest.kt new file mode 100644 index 000000000..c990d8f9b --- /dev/null +++ b/app/src/test/java/io/nekohasekai/sagernet/ui/ShortcutTargetPackageTest.kt @@ -0,0 +1,71 @@ +package io.nekohasekai.sagernet.ui + +import io.nekohasekai.sagernet.R +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config +import org.xmlpull.v1.XmlPullParser + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [35], application = android.app.Application::class) +class ShortcutTargetPackageTest { + + @Test + fun shortcutIntents_targetCurrentApplicationVariant() { + val application = RuntimeEnvironment.getApplication() + val resources = application.resources + val shortcutIds = mutableListOf() + var intentCount = 0 + val parser = resources.getXml(R.xml.shortcuts) + + try { + while (parser.eventType != XmlPullParser.END_DOCUMENT) { + if (parser.eventType == XmlPullParser.START_TAG) { + when (parser.name) { + "shortcut" -> { + val shortcutId = + parser.getAttributeValue(ANDROID_NAMESPACE, "shortcutId").orEmpty() + shortcutIds += shortcutId + } + + "intent" -> { + intentCount++ + assertEquals( + application.packageName, + parser.getAttributeValue(ANDROID_NAMESPACE, "targetPackage"), + ) + assertEquals( + 0, + parser.getAttributeResourceValue( + ANDROID_NAMESPACE, + "targetPackage", + 0, + ), + ) + assertTrue( + parser.getAttributeValue(ANDROID_NAMESPACE, "targetClass") + .orEmpty() + .isNotBlank(), + ) + } + } + } + parser.next() + } + } finally { + parser.close() + } + + assertEquals(4, intentCount) + assertEquals(4, shortcutIds.size) + assertEquals(setOf("toggle", "enable", "disable", "scan"), shortcutIds.toSet()) + } + + private companion object { + const val ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android" + } +} diff --git a/buildSrc/src/main/kotlin/Helpers.kt b/buildSrc/src/main/kotlin/Helpers.kt index 4a50d6918..621f84843 100644 --- a/buildSrc/src/main/kotlin/Helpers.kt +++ b/buildSrc/src/main/kotlin/Helpers.kt @@ -1,8 +1,15 @@ import com.android.build.api.artifact.SingleArtifact import com.android.build.api.dsl.ApplicationExtension import com.android.build.api.variant.ApplicationAndroidComponentsExtension +import org.gradle.api.DefaultTask import org.gradle.api.JavaVersion import org.gradle.api.Project +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.getByName import org.gradle.kotlin.dsl.register import org.gradle.kotlin.dsl.withType @@ -228,6 +235,20 @@ fun Project.setupApp() { // The preview flavor uses PRE_VERSION_NAME to match the previous behaviour. val previewVersionName = requireMetadata().getProperty("PRE_VERSION_NAME") androidComponents.onVariants { variant -> + val shortcutResourcesTask = tasks.register( + "generate${variant.name.replaceFirstChar { it.uppercase() }}ShortcutResources", + ) { + val effectivePackageName = if (variant.buildType == "debug") "$pkgName.debug" else pkgName + targetPackage.set(effectivePackageName) + outputDirectory.set( + layout.buildDirectory.dir("generated/shortcutResources/${variant.name}"), + ) + } + variant.sources.res?.addGeneratedSourceDirectory( + shortcutResourcesTask, + GenerateShortcutResourcesTask::outputDirectory, + ) + val isPreview = variant.flavorName == "preview" || variant.productFlavors.any { (_, flavor) -> flavor == "preview" } val version = if (isPreview) previewVersionName else verName @@ -243,3 +264,25 @@ fun Project.setupApp() { .toListenTo(SingleArtifact.APK) } } + +@CacheableTask +abstract class GenerateShortcutResourcesTask : DefaultTask() { + @get:Input + abstract val targetPackage: Property + + @get:OutputDirectory + abstract val outputDirectory: DirectoryProperty + + @TaskAction + fun generate() { + val xmlDirectory = outputDirectory.dir("xml").get().asFile.apply { mkdirs() } + xmlDirectory.resolve("shortcuts.xml").writeText(renderShortcuts(targetPackage.get())) + } + + private fun renderShortcuts(packageName: String): String { + val template = checkNotNull(javaClass.getResourceAsStream("/shortcuts-template.xml")) { + "shortcuts-template.xml resource not found" + }.bufferedReader().use { it.readText() } + return template.replace("{{TARGET_PACKAGE}}", packageName) + } +} diff --git a/app/src/main/res/xml/shortcuts.xml b/buildSrc/src/main/resources/shortcuts-template.xml similarity index 88% rename from app/src/main/res/xml/shortcuts.xml rename to buildSrc/src/main/resources/shortcuts-template.xml index 94286d108..4c964998a 100644 --- a/app/src/main/res/xml/shortcuts.xml +++ b/buildSrc/src/main/resources/shortcuts-template.xml @@ -8,7 +8,7 @@ + android:targetPackage="{{TARGET_PACKAGE}}" /> + android:targetPackage="{{TARGET_PACKAGE}}" /> + android:targetPackage="{{TARGET_PACKAGE}}" /> + android:targetPackage="{{TARGET_PACKAGE}}" />