diff --git a/app/src/main/java/app/revanced/manager/data/platform/FileSystem.kt b/app/src/main/java/app/revanced/manager/data/platform/Filesystem.kt similarity index 81% rename from app/src/main/java/app/revanced/manager/data/platform/FileSystem.kt rename to app/src/main/java/app/revanced/manager/data/platform/Filesystem.kt index f037e8a6e6..ec01f09ba8 100644 --- a/app/src/main/java/app/revanced/manager/data/platform/FileSystem.kt +++ b/app/src/main/java/app/revanced/manager/data/platform/Filesystem.kt @@ -9,9 +9,18 @@ import androidx.activity.result.contract.ActivityResultContract import androidx.activity.result.contract.ActivityResultContracts import app.revanced.manager.util.RequestManageStorageContract -class FileSystem(private val app: Application) { +class Filesystem(private val app: Application) { val contentResolver = app.contentResolver // TODO: move Content Resolver operations to here. + /** + * A directory that gets cleared when the app restarts. + * Do not store paths to this directory in a parcel. + */ + val tempDir = app.cacheDir.resolve("ephemeral").apply { + deleteRecursively() + mkdirs() + } + fun externalFilesDir() = Environment.getExternalStorageDirectory().toPath() private fun usesManagePermission() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R diff --git a/app/src/main/java/app/revanced/manager/di/RepositoryModule.kt b/app/src/main/java/app/revanced/manager/di/RepositoryModule.kt index 37c152e54c..5b426cbc60 100644 --- a/app/src/main/java/app/revanced/manager/di/RepositoryModule.kt +++ b/app/src/main/java/app/revanced/manager/di/RepositoryModule.kt @@ -1,17 +1,20 @@ package app.revanced.manager.di -import app.revanced.manager.data.platform.FileSystem +import app.revanced.manager.data.platform.Filesystem import app.revanced.manager.data.platform.NetworkInfo import app.revanced.manager.domain.repository.* import app.revanced.manager.domain.worker.WorkerRepository import app.revanced.manager.network.api.ReVancedAPI +import org.koin.core.module.dsl.createdAtStart import org.koin.core.module.dsl.singleOf import org.koin.dsl.module val repositoryModule = module { singleOf(::ReVancedAPI) singleOf(::GithubRepository) - singleOf(::FileSystem) + singleOf(::Filesystem) { + createdAtStart() + } singleOf(::NetworkInfo) singleOf(::PatchBundlePersistenceRepository) singleOf(::PatchSelectionRepository) diff --git a/app/src/main/java/app/revanced/manager/patcher/Session.kt b/app/src/main/java/app/revanced/manager/patcher/Session.kt index 2ed9ea340f..35b80e5e37 100644 --- a/app/src/main/java/app/revanced/manager/patcher/Session.kt +++ b/app/src/main/java/app/revanced/manager/patcher/Session.kt @@ -24,16 +24,17 @@ class Session( private val input: File, private val onStepSucceeded: suspend () -> Unit ) : Closeable { - private val temporary = File(cacheDir).resolve("manager").also { it.mkdirs() } + private val tempDir = File(cacheDir).resolve("patcher").also { it.mkdirs() } private val patcher = Patcher( PatcherOptions( inputFile = input, - resourceCachePath = temporary.resolve("aapt-resources"), + resourceCachePath = tempDir.resolve("aapt-resources"), frameworkFileDirectory = frameworkDir, aaptBinaryPath = aaptPath ) ) + private suspend fun Patcher.applyPatchesVerbose() { this.apply(true).collect { (patch, exception) -> if (exception == null) { @@ -70,7 +71,7 @@ class Session( logger.info("Writing patched files...") val result = patcher.get() - val aligned = temporary.resolve("aligned.apk") + val aligned = tempDir.resolve("aligned.apk") ApkUtils.copyAligned(input, aligned, result) logger.info("Patched apk saved to $aligned") @@ -82,7 +83,7 @@ class Session( } override fun close() { - temporary.delete() + tempDir.deleteRecursively() patcher.close() } diff --git a/app/src/main/java/app/revanced/manager/patcher/worker/PatcherWorker.kt b/app/src/main/java/app/revanced/manager/patcher/worker/PatcherWorker.kt index a5f3ab73e5..864eb343f3 100644 --- a/app/src/main/java/app/revanced/manager/patcher/worker/PatcherWorker.kt +++ b/app/src/main/java/app/revanced/manager/patcher/worker/PatcherWorker.kt @@ -14,6 +14,7 @@ import androidx.core.content.ContextCompat import androidx.work.ForegroundInfo import androidx.work.WorkerParameters import app.revanced.manager.R +import app.revanced.manager.data.platform.Filesystem import app.revanced.manager.data.room.apps.installed.InstallType import app.revanced.manager.domain.installer.RootInstaller import app.revanced.manager.domain.manager.PreferencesManager @@ -49,6 +50,7 @@ class PatcherWorker( private val prefs: PreferencesManager by inject() private val downloadedAppRepository: DownloadedAppRepository by inject() private val pm: PM by inject() + private val fs: Filesystem by inject() private val installedAppRepository: InstalledAppRepository by inject() private val rootInstaller: RootInstaller by inject() @@ -57,13 +59,12 @@ class PatcherWorker( val output: String, val selectedPatches: PatchesSelection, val options: Options, - val packageName: String, - val packageVersion: String, val progress: MutableStateFlow>, val logger: ManagerLogger, - val selectedApp: SelectedApp, val setInputFile: (File) -> Unit - ) + ) { + val packageName get() = input.packageName + } companion object { private const val logPrefix = "[Worker]:" @@ -153,7 +154,7 @@ class PatcherWorker( return try { - if (args.selectedApp is SelectedApp.Installed) { + if (args.input is SelectedApp.Installed) { installedAppRepository.get(args.packageName)?.let { if (it.installType == InstallType.ROOT) { rootInstaller.unmount(args.packageName) @@ -212,7 +213,7 @@ class PatcherWorker( } Session( - applicationContext.cacheDir.absolutePath, + fs.tempDir.absolutePath, frameworkPath, aaptPath, args.logger, diff --git a/app/src/main/java/app/revanced/manager/ui/component/patches/OptionFields.kt b/app/src/main/java/app/revanced/manager/ui/component/patches/OptionFields.kt index f2c00437f4..e8fb4e4ade 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/patches/OptionFields.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/patches/OptionFields.kt @@ -26,7 +26,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import app.revanced.manager.R -import app.revanced.manager.data.platform.FileSystem +import app.revanced.manager.data.platform.Filesystem import app.revanced.manager.patcher.patch.Option import app.revanced.manager.util.toast import app.revanced.patcher.patch.options.types.* @@ -61,7 +61,7 @@ private fun StringOptionDialog( mutableStateOf(value.orEmpty()) } - val fs: FileSystem = rememberKoinInject() + val fs: Filesystem = rememberKoinInject() val (contract, permissionName) = fs.permissionContract() val permissionLauncher = rememberLauncherForActivityResult(contract = contract) { showFileDialog = it diff --git a/app/src/main/java/app/revanced/manager/ui/model/SelectedApp.kt b/app/src/main/java/app/revanced/manager/ui/model/SelectedApp.kt index 424d07e7cd..f5e1b5f842 100644 --- a/app/src/main/java/app/revanced/manager/ui/model/SelectedApp.kt +++ b/app/src/main/java/app/revanced/manager/ui/model/SelectedApp.kt @@ -13,7 +13,7 @@ sealed class SelectedApp : Parcelable { data class Download(override val packageName: String, override val version: String, val app: AppDownloader.App) : SelectedApp() @Parcelize - data class Local(override val packageName: String, override val version: String, val file: File) : SelectedApp() + data class Local(override val packageName: String, override val version: String, val file: File, val shouldDelete: Boolean) : SelectedApp() @Parcelize data class Installed(override val packageName: String, override val version: String) : SelectedApp() diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/AppSelectorViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/AppSelectorViewModel.kt index ff137be81c..62915e0b48 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/AppSelectorViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/AppSelectorViewModel.kt @@ -13,20 +13,27 @@ class AppSelectorViewModel( private val app: Application, private val pm: PM ) : ViewModel() { + private val inputFile = File(app.cacheDir, "input.apk").also { + it.delete() + } val appList = pm.appList fun loadLabel(app: PackageInfo?) = with(pm) { app?.label() ?: "Not installed" } fun loadSelectedFile(uri: Uri) = app.contentResolver.openInputStream(uri)?.use { stream -> - File(app.cacheDir, "input.apk").also { - it.delete() - Files.copy(stream, it.toPath()) - }.let { file -> - pm.getPackageInfo(file) - ?.let { packageInfo -> - SelectedApp.Local(packageName = packageInfo.packageName, version = packageInfo.versionName, file = file) - } + with(inputFile) { + delete() + Files.copy(stream, toPath()) + + pm.getPackageInfo(this)?.let { packageInfo -> + SelectedApp.Local( + packageName = packageInfo.packageName, + version = packageInfo.versionName, + file = this, + shouldDelete = true + ) + } } } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/InstallerViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/InstallerViewModel.kt index a1a5ebd272..1a78078a7c 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/InstallerViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/InstallerViewModel.kt @@ -19,6 +19,7 @@ import androidx.lifecycle.viewModelScope import androidx.work.WorkInfo import androidx.work.WorkManager import app.revanced.manager.R +import app.revanced.manager.data.platform.Filesystem import app.revanced.manager.data.room.apps.installed.InstallType import app.revanced.manager.data.room.apps.installed.InstalledApp import app.revanced.manager.domain.installer.RootInstaller @@ -57,16 +58,22 @@ class InstallerViewModel( ) : ViewModel(), KoinComponent { private val keystoreManager: KeystoreManager by inject() private val app: Application by inject() + private val fs: Filesystem by inject() private val pm: PM by inject() private val workerRepository: WorkerRepository by inject() private val installedAppRepository: InstalledAppRepository by inject() private val rootInstaller: RootInstaller by inject() val packageName: String = input.selectedApp.packageName - private val outputFile = File(app.cacheDir, "output.apk") - private val signedFile = File(app.cacheDir, "signed.apk").also { if (it.exists()) it.delete() } + private val tempDir = fs.tempDir.resolve("installer").also { + it.deleteRecursively() + it.mkdirs() + } + + private val outputFile = tempDir.resolve("output.apk") + private val signedFile = tempDir.resolve("signed.apk") private var hasSigned = false - var inputFile: File? = null + private var inputFile: File? = null private var installedApp: InstalledApp? = null var isInstalling by mutableStateOf(false) @@ -82,6 +89,8 @@ class InstallerViewModel( private val logger = ManagerLogger() init { + // TODO: navigate away when system-initiated process death is detected because it is not possible to recover from it. + viewModelScope.launch { installedApp = installedAppRepository.get(packageName) } @@ -101,11 +110,8 @@ class InstallerViewModel( outputFile.path, patches, options, - packageName, - selectedApp.version, _progress, logger, - selectedApp, setInputFile = { inputFile = it } ) ) @@ -176,8 +182,14 @@ class InstallerViewModel( app.unregisterReceiver(installBroadcastReceiver) workManager.cancelWorkById(patcherWorkerId) - outputFile.delete() - signedFile.delete() + when (val selectedApp = input.selectedApp) { + is SelectedApp.Local -> { + if (selectedApp.shouldDelete) selectedApp.file.delete() + } + else -> {} + } + + tempDir.deleteRecursively() try { if (input.selectedApp is SelectedApp.Installed) { diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/VersionSelectorViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/VersionSelectorViewModel.kt index 7b2f8b738b..f7420131d2 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/VersionSelectorViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/VersionSelectorViewModel.kt @@ -68,7 +68,7 @@ class VersionSelectorViewModel( } val downloadedVersions = downloadedAppRepository.getAll().map { downloadedApps -> - downloadedApps.filter { it.packageName == packageName }.map { SelectedApp.Local(it.packageName, it.version, it.file) } + downloadedApps.filter { it.packageName == packageName }.map { SelectedApp.Local(it.packageName, it.version, it.file, false) } } init {