diff --git a/application/src/main/kotlin/com/baidaidai/rootless_store/application/environment/InstallEnvironmentUseCase.kt b/application/src/main/kotlin/com/baidaidai/rootless_store/application/environment/InstallEnvironmentUseCase.kt index 5ab3f1d4..9d7413b7 100644 --- a/application/src/main/kotlin/com/baidaidai/rootless_store/application/environment/InstallEnvironmentUseCase.kt +++ b/application/src/main/kotlin/com/baidaidai/rootless_store/application/environment/InstallEnvironmentUseCase.kt @@ -1,6 +1,7 @@ package com.baidaidai.rootless_store.application.environment import com.baidaidai.rootless_store.core.util.formatAsMultilineString +import com.baidaidai.rootless_store.data.fileSystem.gateway.AndroidFileSystemDefaultOperatorGatewayImpl import com.baidaidai.rootless_store.data.environment.gateway.EnvironmentGatewayImpl import com.baidaidai.rootless_store.data.environment.repository.EnvironmentRepositoryImpl import com.baidaidai.rootless_store.data.environment.repository.EnvironmentStatusRepositoryImpl @@ -14,10 +15,20 @@ import javax.inject.Inject import java.io.File class InstallEnvironmentUseCase @Inject constructor( + private val androidFileSystemDefaultOperatorGatewayImpl: AndroidFileSystemDefaultOperatorGatewayImpl, private val environmentGatewayImpl: EnvironmentGatewayImpl, private val environmentRepositoryImpl: EnvironmentRepositoryImpl, private val environmentStatusRepositoryImpl: EnvironmentStatusRepositoryImpl ) { + + suspend operator fun invoke(): Result { + val packageDirectory = androidFileSystemDefaultOperatorGatewayImpl + .getExternalAppPackageCacheDirectoryFile() + val localPackageFile = File(packageDirectory, "package.zip") + + return invoke(localPackageFile) + } + suspend operator fun invoke(packageFile: File): Result { val environmentManifest: EnvironmentManifest = environmentGatewayImpl .parseEnvironmentManifest(packageFile) diff --git a/application/src/main/kotlin/com/baidaidai/rootless_store/application/plugin/InstallPluginUseCase.kt b/application/src/main/kotlin/com/baidaidai/rootless_store/application/plugin/InstallPluginUseCase.kt index 2418c64c..d63c6d79 100644 --- a/application/src/main/kotlin/com/baidaidai/rootless_store/application/plugin/InstallPluginUseCase.kt +++ b/application/src/main/kotlin/com/baidaidai/rootless_store/application/plugin/InstallPluginUseCase.kt @@ -1,6 +1,7 @@ package com.baidaidai.rootless_store.application.plugin import com.baidaidai.rootless_store.core.util.formatAsMultilineString +import com.baidaidai.rootless_store.data.fileSystem.gateway.AndroidFileSystemDefaultOperatorGatewayImpl import com.baidaidai.rootless_store.data.plugin.gateway.PluginGatewayImpl import com.baidaidai.rootless_store.data.plugin.repository.PluginRepositoryImpl import com.baidaidai.rootless_store.data.plugin.repository.PluginStatusRepositoryImpl @@ -15,11 +16,21 @@ import javax.inject.Inject import java.io.File class InstallPluginUseCase @Inject constructor( + private val androidFileSystemDefaultOperatorGatewayImpl: AndroidFileSystemDefaultOperatorGatewayImpl, private val pluginRepositoryImpl: PluginRepositoryImpl, private val pluginStatusRepositoryImpl: PluginStatusRepositoryImpl, private val pluginGatewayImpl: PluginGatewayImpl, private val installShellPluginUseCase: InstallShellPluginUseCase ) { + + suspend operator fun invoke(): Result { + val packageDirectory = androidFileSystemDefaultOperatorGatewayImpl + .getExternalAppPackageCacheDirectoryFile() + val localPackageFile = File(packageDirectory, "package.zip") + + return invoke(localPackageFile) + } + suspend operator fun invoke(packageFile: File): Result { val pluginManifest: PluginManifest = pluginGatewayImpl diff --git a/data/src/main/kotlin/com/baidaidai/rootless_store/data/signature/gateway/PackageSignatureGatewayImpl.kt b/data/src/main/kotlin/com/baidaidai/rootless_store/data/signature/gateway/PackageSignatureGatewayImpl.kt index 241ef97b..0b2e2a60 100644 --- a/data/src/main/kotlin/com/baidaidai/rootless_store/data/signature/gateway/PackageSignatureGatewayImpl.kt +++ b/data/src/main/kotlin/com/baidaidai/rootless_store/data/signature/gateway/PackageSignatureGatewayImpl.kt @@ -5,6 +5,7 @@ import android.util.Log import com.baidaidai.rootless_store.data.R import com.baidaidai.rootless_store.data.fileSystem.gateway.AndroidFileSystemReadOperatorGatewayImpl import com.baidaidai.rootless_store.data.signature.repository.CaCertificateRepositoryImpl +import com.baidaidai.rootless_store.domain.install.model.LocalPackageType import com.baidaidai.rootless_store.domain.install.model.PackageSignatureInfo import com.baidaidai.rootless_store.domain.install.model.PackageSignatureStatus import dagger.hilt.android.qualifiers.ApplicationContext @@ -84,11 +85,8 @@ class PackageSignatureGatewayImpl @Inject constructor( * * @since 2026.10.26 * @version 0.1.0 - * - * - * */ - private suspend fun hasTrustedContentSignature( + internal suspend fun hasTrustedContentSignature( packageBytes: ByteArray, signatureBytes: ByteArray ): Boolean { @@ -170,8 +168,6 @@ class PackageSignatureGatewayImpl @Inject constructor( * 那就直接跳出去吧 * * 这一步只浅筛,是否有有效签名者,不进行内容计算 - * - * Holder只是指针引用,保存了 Cert 的原始记录 */ val signerCertificateHolder = cmsCertificateList .firstOrNull { certificateHolder -> @@ -185,7 +181,6 @@ class PackageSignatureGatewayImpl @Inject constructor( signerCertificateHolder ) - // 签名者必须是有效期内的非 CA 证书,并允许数字签名用途。 signerCertificate.checkValidity() // 验证 CMS 内容签名 @@ -239,7 +234,7 @@ class PackageSignatureGatewayImpl @Inject constructor( * @since 2026.10.26 * @version 0.1.0 */ - private suspend fun loadTrustedCaCertificates(): Set { + internal suspend fun loadTrustedCaCertificates(): Set { Log.d(logTag, "Loading built-in and enabled custom CA certificates") val trustedCaCertificates = mutableSetOf() @@ -290,8 +285,19 @@ class PackageSignatureGatewayImpl @Inject constructor( } - // 将 CMS 携带的中间证书链构建到任一已启用的 CA 信任锚。 - private fun hasTrustedCertificatePath( + /** + * 检查签名者证书能否构建到受信任的 CA 锚点。 + * + * 使用 Bouncy Castle 的 PKIX CertPathBuilder,以签名者证书作为目标, + * 尝试从提供的信任锚集合中构建证书路径;此处不执行证书吊销检查。 + * + * @param signerCertificate CMS 签名者证书 + * @param trustedCaCertificates 可作为信任锚的 CA 证书集合 + * @return 成功构建证书路径时为 true,否则为 false + * @since 2026.10.26 + * @version 0.1.0 + */ + internal fun hasTrustedCertificatePath( signerCertificate: X509Certificate, trustedCaCertificates: Set ): Boolean { @@ -338,6 +344,7 @@ class PackageSignatureGatewayImpl @Inject constructor( if (pluginManifest != null) { return PackageSignatureInfo( + packageType = LocalPackageType.Plugin, displayName = pluginManifest.pluginRenderingName, author = pluginManifest.author, validSigner = signatureStatus == PackageSignatureStatus.Trusted, @@ -354,6 +361,7 @@ class PackageSignatureGatewayImpl @Inject constructor( ?: error("Signed package did not contain a supported Plugin or Environment manifest") return PackageSignatureInfo( + packageType = LocalPackageType.Environment, displayName = environmentManifest.environmentRenderingName, author = environmentManifest.author, validSigner = signatureStatus == PackageSignatureStatus.Trusted, diff --git a/domain/src/main/kotlin/com/baidaidai/rootless_store/domain/install/model/PackageSignatureInfo.kt b/domain/src/main/kotlin/com/baidaidai/rootless_store/domain/install/model/PackageSignatureInfo.kt index 1763f920..76ecc009 100644 --- a/domain/src/main/kotlin/com/baidaidai/rootless_store/domain/install/model/PackageSignatureInfo.kt +++ b/domain/src/main/kotlin/com/baidaidai/rootless_store/domain/install/model/PackageSignatureInfo.kt @@ -1,6 +1,7 @@ package com.baidaidai.rootless_store.domain.install.model data class PackageSignatureInfo( + val packageType: LocalPackageType, val displayName: String, val author: String, val validSigner: Boolean = false, diff --git a/ui/src/main/kotlin/com/baidaidai/rootless_store/ui/components/pluginScreen/PluginBottomSheetContent.kt b/ui/src/main/kotlin/com/baidaidai/rootless_store/ui/components/pluginScreen/PluginBottomSheetContent.kt index 48ebe970..d75f1a63 100644 --- a/ui/src/main/kotlin/com/baidaidai/rootless_store/ui/components/pluginScreen/PluginBottomSheetContent.kt +++ b/ui/src/main/kotlin/com/baidaidai/rootless_store/ui/components/pluginScreen/PluginBottomSheetContent.kt @@ -30,8 +30,10 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import androidx.core.net.toUri +import com.baidaidai.rootless_store.domain.install.model.LocalPackageType import com.baidaidai.rootless_store.domain.install.model.PackageSignatureInfo import com.baidaidai.rootless_store.ui.R +import com.baidaidai.rootless_store.ui.components.common.VerticalSpacer import com.baidaidai.rootless_store.ui.theme.ColorFactory import com.baidaidai.rootless_store.ui.theme.SuccessColorSet import com.baidaidai.rootless_store.ui.theme.WarningColorSet @@ -197,9 +199,7 @@ fun PluginBottomSheetContent( ) } - androidx.compose.foundation.layout.Spacer( - modifier = Modifier.height(12.dp) - ) + VerticalSpacer(20.dp) Text( text = packageDescription, @@ -209,9 +209,7 @@ fun PluginBottomSheetContent( fontWeight = FontWeight(440) ) - androidx.compose.foundation.layout.Spacer( - modifier = Modifier.height(20.dp) - ) + VerticalSpacer(20.dp) Row( horizontalArrangement = Arrangement.spacedBy(12.dp), @@ -263,6 +261,7 @@ fun PluginBottomSheetContent( private fun _preview_() { PluginBottomSheetContent( packageSignatureInfo = PackageSignatureInfo( + packageType = LocalPackageType.Plugin, displayName = "Demo Plugin", author = "Rootless Store", validSigner = false, diff --git a/ui/src/main/kotlin/com/baidaidai/rootless_store/ui/model/RootlessStorePluginScreenViewModel.kt b/ui/src/main/kotlin/com/baidaidai/rootless_store/ui/model/RootlessStorePluginScreenViewModel.kt index 3f87fb8c..3eb401f1 100644 --- a/ui/src/main/kotlin/com/baidaidai/rootless_store/ui/model/RootlessStorePluginScreenViewModel.kt +++ b/ui/src/main/kotlin/com/baidaidai/rootless_store/ui/model/RootlessStorePluginScreenViewModel.kt @@ -19,11 +19,14 @@ import com.baidaidai.rootless_store.application.environment.ObserveEnvironmentsU import com.baidaidai.rootless_store.application.environment.ObserveEnvironmentStatusUseCase import com.baidaidai.rootless_store.application.environment.DisableEnvironmentUseCase import com.baidaidai.rootless_store.application.environment.EnableEnvironmentUseCase +import com.baidaidai.rootless_store.application.environment.InstallEnvironmentUseCase import com.baidaidai.rootless_store.application.plugin.DisablePluginUseCase import com.baidaidai.rootless_store.application.plugin.EnablePluginUseCase import com.baidaidai.rootless_store.application.environment.UninstallEnvironmentUseCase +import com.baidaidai.rootless_store.application.plugin.InstallPluginUseCase import com.baidaidai.rootless_store.application.plugin.ResolvePluginShareUriUseCase import com.baidaidai.rootless_store.application.plugin.ResolvePluginWebUiUriUseCase +import com.baidaidai.rootless_store.domain.install.model.LocalPackageType import com.baidaidai.rootless_store.domain.plugin.manifest.PluginManifest import com.baidaidai.rootless_store.domain.plugin.model.PluginStatus import com.baidaidai.rootless_store.ui.uistate.RootlessStorePluginScreenUiState @@ -49,6 +52,8 @@ class RootlessStorePluginScreenViewModel @Inject constructor( private val observeEnvironmentStatusUseCase: ObserveEnvironmentStatusUseCase, private val observePluginStatusUseCase: ObservePluginStatusUseCase, private val installLocalPackageUseCase: InstallLocalPackageUseCase, + private val installPluginUseCase: InstallPluginUseCase, + private val installEnvironmentUseCase: InstallEnvironmentUseCase, private val enablePluginUseCase: EnablePluginUseCase, private val disablePluginUseCase: DisablePluginUseCase, private val enableEnvironmentUseCase: EnableEnvironmentUseCase, @@ -132,9 +137,17 @@ class RootlessStorePluginScreenViewModel @Inject constructor( } fun confirmLocalPackageInstall() { + val packageSignatureInfo = _pluginScreenUiState.value.packageSignatureInfo clearLocalPackageInstallConfirmation() - installLocalPackage() + + viewModelScope.launch { + when (packageSignatureInfo!!.packageType) { + LocalPackageType.Plugin -> installPluginUseCase() + else -> installEnvironmentUseCase() + } + } } + fun dismissLocalPackageInstallConfirmation() { clearLocalPackageInstallConfirmation() viewModelScope.launch {