Merge synonym plugin to downloader

This commit is contained in:
oSumAtrIX
2026-01-08 22:00:04 +01:00
parent 25d82e869c
commit 8d0ee814fc
41 changed files with 384 additions and 399 deletions

View File

@@ -3,13 +3,13 @@
xmlns:tools="http://schemas.android.com/tools">
<permission
android:name="app.revanced.manager.permission.PLUGIN_HOST"
android:name="app.revanced.manager.permission.DOWNLOADER_HOST"
android:protectionLevel="signature"
android:label="@string/plugin_host_permission_label"
android:description="@string/plugin_host_permission_description"
android:label="@string/downloader_host_permission_label"
android:description="@string/downloader_host_permission_description"
/>
<uses-permission android:name="app.revanced.manager.permission.PLUGIN_HOST" />
<uses-permission android:name="app.revanced.manager.permission.DOWNLOADER_HOST" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -49,7 +49,7 @@
</intent-filter>
</activity>
<activity android:name=".plugin.downloader.webview.WebViewActivity" android:exported="false" android:theme="@style/Theme.WebViewActivity" />
<activity android:name=".downloader.webview.WebViewActivity" android:exported="false" android:theme="@style/Theme.WebViewActivity" />
<service
android:name="androidx.work.impl.foreground.SystemForegroundService"

View File

@@ -129,7 +129,7 @@ private fun ReVancedManager(vm: MainViewModel) {
onUpdateClick = {
navController.navigate(Update())
},
onDownloaderPluginClick = {
onDownloaderClick = {
navController.navigate(Settings.Downloads)
},
onAppClick = { packageName ->

View File

@@ -7,7 +7,7 @@ import android.util.Log
import app.revanced.manager.data.platform.Filesystem
import app.revanced.manager.di.*
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.repository.DownloaderPluginRepository
import app.revanced.manager.domain.repository.DownloaderRepository
import app.revanced.manager.domain.repository.PatchBundleRepository
import app.revanced.manager.util.tag
import kotlinx.coroutines.Dispatchers
@@ -29,7 +29,7 @@ class ManagerApplication : Application() {
private val scope = MainScope()
private val prefs: PreferencesManager by inject()
private val patchBundleRepository: PatchBundleRepository by inject()
private val downloaderPluginRepository: DownloaderPluginRepository by inject()
private val downloaderRepository: DownloaderRepository by inject()
private val fs: Filesystem by inject()
override fun onCreate() {
@@ -70,7 +70,7 @@ class ManagerApplication : Application() {
prefs.preload()
}
scope.launch(Dispatchers.Default) {
downloaderPluginRepository.reload()
downloaderRepository.reload()
}
scope.launch(Dispatchers.Default) {
with(patchBundleRepository) {

View File

@@ -16,12 +16,12 @@ import app.revanced.manager.data.room.bundles.PatchBundleEntity
import app.revanced.manager.data.room.options.Option
import app.revanced.manager.data.room.options.OptionDao
import app.revanced.manager.data.room.options.OptionGroup
import app.revanced.manager.data.room.plugins.TrustedDownloaderPlugin
import app.revanced.manager.data.room.plugins.TrustedDownloaderPluginDao
import app.revanced.manager.data.room.downloader.TrustedDownloader
import app.revanced.manager.data.room.downloader.TrustedDownloaderDao
import kotlin.random.Random
@Database(
entities = [PatchBundleEntity::class, PatchSelection::class, SelectedPatch::class, DownloadedApp::class, InstalledApp::class, AppliedPatch::class, OptionGroup::class, Option::class, TrustedDownloaderPlugin::class],
entities = [PatchBundleEntity::class, PatchSelection::class, SelectedPatch::class, DownloadedApp::class, InstalledApp::class, AppliedPatch::class, OptionGroup::class, Option::class, TrustedDownloader::class],
version = 1
)
@TypeConverters(Converters::class)
@@ -31,7 +31,7 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun downloadedAppDao(): DownloadedAppDao
abstract fun installedAppDao(): InstalledAppDao
abstract fun optionDao(): OptionDao
abstract fun trustedDownloaderPluginDao(): TrustedDownloaderPluginDao
abstract fun trustedDownloaderDao(): TrustedDownloaderDao
companion object {
fun generateUid() = Random.Default.nextInt()

View File

@@ -1,11 +1,11 @@
package app.revanced.manager.data.room.plugins
package app.revanced.manager.data.room.downloader
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "trusted_downloader_plugins")
class TrustedDownloaderPlugin(
@Entity(tableName = "trusted_downloader")
class TrustedDownloader(
@PrimaryKey @ColumnInfo(name = "package_name") val packageName: String,
@ColumnInfo(name = "signature") val signature: ByteArray
)

View File

@@ -0,0 +1,22 @@
package app.revanced.manager.data.room.downloader
import androidx.room.Dao
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Upsert
@Dao
interface TrustedDownloaderDao {
@Query("SELECT signature FROM trusted_downloader WHERE package_name = :packageName")
suspend fun getTrustedSignature(packageName: String): ByteArray?
@Upsert
suspend fun upsertTrust(downloader: TrustedDownloader)
@Query("DELETE FROM trusted_downloader WHERE package_name = :packageName")
suspend fun remove(packageName: String)
@Transaction
@Query("DELETE FROM trusted_downloader WHERE package_name IN (:packageNames)")
suspend fun removeAll(packageNames: Set<String>)
}

View File

@@ -1,22 +0,0 @@
package app.revanced.manager.data.room.plugins
import androidx.room.Dao
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Upsert
@Dao
interface TrustedDownloaderPluginDao {
@Query("SELECT signature FROM trusted_downloader_plugins WHERE package_name = :packageName")
suspend fun getTrustedSignature(packageName: String): ByteArray?
@Upsert
suspend fun upsertTrust(plugin: TrustedDownloaderPlugin)
@Query("DELETE FROM trusted_downloader_plugins WHERE package_name = :packageName")
suspend fun remove(packageName: String)
@Transaction
@Query("DELETE FROM trusted_downloader_plugins WHERE package_name IN (:packageNames)")
suspend fun removeAll(packageNames: Set<String>)
}

View File

@@ -21,7 +21,7 @@ val repositoryModule = module {
// It is best to load patch bundles ASAP
createdAtStart()
}
singleOf(::DownloaderPluginRepository)
singleOf(::DownloaderRepository)
singleOf(::WorkerRepository)
singleOf(::DownloadedAppRepository)
singleOf(::InstalledAppRepository)

View File

@@ -31,7 +31,7 @@ class PreferencesManager(
val disableUniversalPatchCheck = booleanPreference("disable_patch_universal_check", false)
val suggestedVersionSafeguard = booleanPreference("suggested_version_safeguard", true)
val acknowledgedDownloaderPlugins = stringSetPreference("acknowledged_downloader_plugins", emptySet())
val acknowledgedDownloader = stringSetPreference("acknowledged_downloader", emptySet())
val showDeveloperSettings = booleanPreference("show_developer_settings", context.isDebuggable)

View File

@@ -6,8 +6,8 @@ import android.os.Parcelable
import app.revanced.manager.data.room.AppDatabase
import app.revanced.manager.data.room.AppDatabase.Companion.generateUid
import app.revanced.manager.data.room.apps.downloaded.DownloadedApp
import app.revanced.manager.network.downloader.LoadedDownloaderPlugin
import app.revanced.manager.plugin.downloader.OutputDownloadScope
import app.revanced.manager.network.downloader.LoadedDownloader
import app.revanced.manager.downloader.OutputDownloadScope
import app.revanced.manager.util.PM
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.channelFlow
@@ -36,7 +36,7 @@ class DownloadedAppRepository(
private fun getApkFileForDir(directory: File) = directory.listFiles()!!.first()
suspend fun download(
plugin: LoadedDownloaderPlugin,
downloader: LoadedDownloader,
data: Parcelable,
expectedPackageName: String,
expectedVersion: String?,
@@ -55,7 +55,7 @@ class DownloadedAppRepository(
channelFlow {
val scope = object : OutputDownloadScope {
override val pluginPackageName = plugin.packageName
override val downloaderPackageName = downloader.packageName
override val hostPackageName = app.packageName
override suspend fun reportSize(size: Long) {
require(size > 0) { "Size must be greater than zero" }
@@ -87,7 +87,7 @@ class DownloadedAppRepository(
)
}
}
plugin.download(scope, data, stream)
downloader.download(scope, data, stream)
}
}
.conflate()

View File

@@ -5,14 +5,14 @@ import android.content.pm.PackageManager
import android.os.Parcelable
import android.util.Log
import app.revanced.manager.data.room.AppDatabase
import app.revanced.manager.data.room.plugins.TrustedDownloaderPlugin
import app.revanced.manager.data.room.downloader.TrustedDownloader
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.network.downloader.DownloaderPluginState
import app.revanced.manager.network.downloader.LoadedDownloaderPlugin
import app.revanced.manager.network.downloader.DownloaderState
import app.revanced.manager.network.downloader.LoadedDownloader
import app.revanced.manager.network.downloader.ParceledDownloaderData
import app.revanced.manager.plugin.downloader.DownloaderBuilder
import app.revanced.manager.plugin.downloader.PluginHostApi
import app.revanced.manager.plugin.downloader.Scope
import app.revanced.manager.downloader.DownloaderBuilder
import app.revanced.manager.downloader.DownloaderHostApi
import app.revanced.manager.downloader.Scope
import app.revanced.manager.util.PM
import app.revanced.manager.util.tag
import dalvik.system.PathClassLoader
@@ -25,74 +25,74 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import java.lang.reflect.Modifier
@OptIn(PluginHostApi::class)
class DownloaderPluginRepository(
@OptIn(DownloaderHostApi::class)
class DownloaderRepository(
private val pm: PM,
private val prefs: PreferencesManager,
private val app: Application,
db: AppDatabase
) {
private val trustDao = db.trustedDownloaderPluginDao()
private val _pluginStates = MutableStateFlow(emptyMap<String, DownloaderPluginState>())
val pluginStates = _pluginStates.asStateFlow()
val loadedPluginsFlow = pluginStates.map { states ->
states.values.filterIsInstance<DownloaderPluginState.Loaded>().map { it.plugin }
private val trustDao = db.trustedDownloaderDao()
private val _downloaderStates = MutableStateFlow(emptyMap<String, DownloaderState>())
val downloaderStates = _downloaderStates.asStateFlow()
val loadedDownloaderFlow = downloaderStates.map { states ->
states.values.filterIsInstance<DownloaderState.Loaded>().map { it.downloader }
}
private val acknowledgedDownloaderPlugins = prefs.acknowledgedDownloaderPlugins
private val installedPluginPackageNames = MutableStateFlow(emptySet<String>())
val newPluginPackageNames = combine(
installedPluginPackageNames,
acknowledgedDownloaderPlugins.flow
private val acknowledgedDownloader = prefs.acknowledgedDownloader
private val installedDownloaderPackageNames = MutableStateFlow(emptySet<String>())
val newDownloaderPackageNames = combine(
installedDownloaderPackageNames,
acknowledgedDownloader.flow
) { installed, acknowledged ->
installed subtract acknowledged
}
suspend fun reload() {
val plugins =
val downloader =
withContext(Dispatchers.IO) {
pm.getPackagesWithFeature(PLUGIN_FEATURE)
.associate { it.packageName to loadPlugin(it.packageName) }
pm.getPackagesWithFeature(DOWNLOADER_FEATURE)
.associate { it.packageName to loadDownloader(it.packageName) }
}
_pluginStates.value = plugins
installedPluginPackageNames.value = plugins.keys
_downloaderStates.value = downloader
installedDownloaderPackageNames.value = downloader.keys
val acknowledgedPlugins = acknowledgedDownloaderPlugins.get()
val uninstalledPlugins = acknowledgedPlugins subtract installedPluginPackageNames.value
if (uninstalledPlugins.isNotEmpty()) {
Log.d(tag, "Uninstalled plugins: ${uninstalledPlugins.joinToString(", ")}")
acknowledgedDownloaderPlugins.update(acknowledgedPlugins subtract uninstalledPlugins)
trustDao.removeAll(uninstalledPlugins)
val acknowledgedDownloader = this@DownloaderRepository.acknowledgedDownloader.get()
val uninstalledDownloader = acknowledgedDownloader subtract installedDownloaderPackageNames.value
if (uninstalledDownloader.isNotEmpty()) {
Log.d(tag, "Uninstalled downloader: ${uninstalledDownloader.joinToString(", ")}")
this@DownloaderRepository.acknowledgedDownloader.update(acknowledgedDownloader subtract uninstalledDownloader)
trustDao.removeAll(uninstalledDownloader)
}
}
fun unwrapParceledData(data: ParceledDownloaderData): Pair<LoadedDownloaderPlugin, Parcelable> {
val plugin =
(_pluginStates.value[data.pluginPackageName] as? DownloaderPluginState.Loaded)?.plugin
?: throw Exception("Downloader plugin with name ${data.pluginPackageName} is not available")
fun unwrapParceledData(data: ParceledDownloaderData): Pair<LoadedDownloader, Parcelable> {
val downloader =
(_downloaderStates.value[data.downloaderPackageName] as? DownloaderState.Loaded)?.downloader
?: throw Exception("Downloader with name ${data.downloaderPackageName} is not available")
return plugin to data.unwrapWith(plugin)
return downloader to data.unwrapWith(downloader)
}
private suspend fun loadPlugin(packageName: String): DownloaderPluginState {
private suspend fun loadDownloader(packageName: String): DownloaderState {
try {
if (!verify(packageName)) return DownloaderPluginState.Untrusted
if (!verify(packageName)) return DownloaderState.Untrusted
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
Log.e(tag, "Got exception while verifying plugin $packageName", e)
return DownloaderPluginState.Failed(e)
Log.e(tag, "Got exception while verifying downloader $packageName", e)
return DownloaderState.Failed(e)
}
return try {
val packageInfo = pm.getPackageInfo(packageName, flags = PackageManager.GET_META_DATA)!!
val className = packageInfo.applicationInfo!!.metaData.getString(METADATA_PLUGIN_CLASS)
?: throw Exception("Missing metadata attribute $METADATA_PLUGIN_CLASS")
val className = packageInfo.applicationInfo!!.metaData.getString(METADATA_DOWNLOADER_CLASS)
?: throw Exception("Missing metadata attribute $METADATA_DOWNLOADER_CLASS")
val classLoader =
PathClassLoader(packageInfo.applicationInfo!!.sourceDir, app.classLoader)
val pluginContext = app.createPackageContext(packageName, 0)
val downloaderContext = app.createPackageContext(packageName, 0)
val downloader = classLoader
.loadClass(className)
@@ -100,13 +100,13 @@ class DownloaderPluginRepository(
.build(
scopeImpl = object : Scope {
override val hostPackageName = app.packageName
override val pluginPackageName = pluginContext.packageName
override val downloaderPackageName = downloaderContext.packageName
},
context = pluginContext
context = downloaderContext
)
DownloaderPluginState.Loaded(
LoadedDownloaderPlugin(
DownloaderState.Loaded(
LoadedDownloader(
packageName,
with(pm) { packageInfo.label() },
packageInfo.versionName!!,
@@ -118,14 +118,14 @@ class DownloaderPluginRepository(
} catch (e: CancellationException) {
throw e
} catch (t: Throwable) {
Log.e(tag, "Failed to load plugin $packageName", t)
DownloaderPluginState.Failed(t)
Log.e(tag, "Failed to load downloader $packageName", t)
DownloaderState.Failed(t)
}
}
suspend fun trustPackage(packageName: String) {
trustDao.upsertTrust(
TrustedDownloaderPlugin(
TrustedDownloader(
packageName,
pm.getSignature(packageName).toByteArray()
)
@@ -133,15 +133,15 @@ class DownloaderPluginRepository(
reload()
prefs.edit {
acknowledgedDownloaderPlugins += packageName
acknowledgedDownloader += packageName
}
}
suspend fun revokeTrustForPackage(packageName: String) =
trustDao.remove(packageName).also { reload() }
suspend fun acknowledgeAllNewPlugins() =
acknowledgedDownloaderPlugins.update(installedPluginPackageNames.value)
suspend fun acknowledgeAllNewDownloader() =
acknowledgedDownloader.update(installedDownloaderPackageNames.value)
private suspend fun verify(packageName: String): Boolean {
val expectedSignature =
@@ -151,8 +151,8 @@ class DownloaderPluginRepository(
}
private companion object {
const val PLUGIN_FEATURE = "app.revanced.manager.plugin.downloader"
const val METADATA_PLUGIN_CLASS = "app.revanced.manager.plugin.downloader.class"
const val DOWNLOADER_FEATURE = "app.revanced.manager.downloader"
const val METADATA_DOWNLOADER_CLASS = "app.revanced.manager.downloader.class"
const val PUBLIC_STATIC = Modifier.PUBLIC or Modifier.STATIC
val Int.isPublicStatic get() = (this and PUBLIC_STATIC) == PUBLIC_STATIC

View File

@@ -1,9 +0,0 @@
package app.revanced.manager.network.downloader
sealed interface DownloaderPluginState {
data object Untrusted : DownloaderPluginState
data class Loaded(val plugin: LoadedDownloaderPlugin) : DownloaderPluginState
data class Failed(val throwable: Throwable) : DownloaderPluginState
}

View File

@@ -0,0 +1,9 @@
package app.revanced.manager.network.downloader
sealed interface DownloaderState {
data object Untrusted : DownloaderState
data class Loaded(val downloader: LoadedDownloader) : DownloaderState
data class Failed(val throwable: Throwable) : DownloaderState
}

View File

@@ -1,11 +1,11 @@
package app.revanced.manager.network.downloader
import android.os.Parcelable
import app.revanced.manager.plugin.downloader.OutputDownloadScope
import app.revanced.manager.plugin.downloader.GetScope
import app.revanced.manager.downloader.OutputDownloadScope
import app.revanced.manager.downloader.GetScope
import java.io.OutputStream
class LoadedDownloaderPlugin(
class LoadedDownloader(
val packageName: String,
val name: String,
val version: String,

View File

@@ -10,20 +10,20 @@ import kotlinx.parcelize.Parcelize
* A container for [Parcelable] data returned from downloader. Instances of this class can be safely stored in a bundle without needing to set the [ClassLoader].
*/
class ParceledDownloaderData private constructor(
val pluginPackageName: String,
val downloaderPackageName: String,
private val bundle: Bundle
) : Parcelable {
constructor(plugin: LoadedDownloaderPlugin, data: Parcelable) : this(
plugin.packageName,
constructor(downloader: LoadedDownloader, data: Parcelable) : this(
downloader.packageName,
createBundle(data)
)
fun unwrapWith(plugin: LoadedDownloaderPlugin): Parcelable {
bundle.classLoader = plugin.classLoader
fun unwrapWith(downloader: LoadedDownloader): Parcelable {
bundle.classLoader = downloader.classLoader
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val className = bundle.getString(CLASS_NAME_KEY)!!
val clazz = plugin.classLoader.loadClass(className)
val clazz = downloader.classLoader.loadClass(className)
bundle.getParcelable(DATA_KEY, clazz)!! as Parcelable
} else @Suppress("Deprecation") bundle.getParcelable(DATA_KEY)!!

View File

@@ -24,11 +24,11 @@ import app.revanced.manager.domain.installer.RootInstaller
import app.revanced.manager.domain.manager.KeystoreManager
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.repository.DownloadedAppRepository
import app.revanced.manager.domain.repository.DownloaderPluginRepository
import app.revanced.manager.domain.repository.DownloaderRepository
import app.revanced.manager.domain.repository.InstalledAppRepository
import app.revanced.manager.domain.worker.Worker
import app.revanced.manager.domain.worker.WorkerRepository
import app.revanced.manager.network.downloader.LoadedDownloaderPlugin
import app.revanced.manager.network.downloader.LoadedDownloader
import app.revanced.manager.patcher.ProgressEvent
import app.revanced.manager.patcher.StepId
import app.revanced.manager.patcher.logger.Logger
@@ -36,9 +36,9 @@ import app.revanced.manager.patcher.runStep
import app.revanced.manager.patcher.runtime.CoroutineRuntime
import app.revanced.manager.patcher.runtime.ProcessRuntime
import app.revanced.manager.patcher.toRemoteError
import app.revanced.manager.plugin.downloader.GetScope
import app.revanced.manager.plugin.downloader.PluginHostApi
import app.revanced.manager.plugin.downloader.UserInteractionException
import app.revanced.manager.downloader.GetScope
import app.revanced.manager.downloader.DownloaderHostApi
import app.revanced.manager.downloader.UserInteractionException
import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.util.Options
import app.revanced.manager.util.PM
@@ -51,7 +51,7 @@ import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import java.io.File
@OptIn(PluginHostApi::class)
@OptIn(DownloaderHostApi::class)
class PatcherWorker(
context: Context,
parameters: WorkerParameters
@@ -59,7 +59,7 @@ class PatcherWorker(
private val workerRepository: WorkerRepository by inject()
private val prefs: PreferencesManager by inject()
private val keystoreManager: KeystoreManager by inject()
private val downloaderPluginRepository: DownloaderPluginRepository by inject()
private val downloaderRepository: DownloaderRepository by inject()
private val downloadedAppRepository: DownloadedAppRepository by inject()
private val pm: PM by inject()
private val fs: Filesystem by inject()
@@ -72,7 +72,7 @@ class PatcherWorker(
val selectedPatches: PatchSelection,
val options: Options,
val logger: Logger,
val handleStartActivityRequest: suspend (LoadedDownloaderPlugin, Intent) -> ActivityResult,
val handleStartActivityRequest: suspend (LoadedDownloader, Intent) -> ActivityResult,
val setInputFile: suspend (File) -> Unit,
val onEvent: (ProgressEvent) -> Unit,
) {
@@ -150,9 +150,9 @@ class PatcherWorker(
}
}
suspend fun download(plugin: LoadedDownloaderPlugin, data: Parcelable) =
suspend fun download(downloader: LoadedDownloader, data: Parcelable) =
downloadedAppRepository.download(
plugin,
downloader,
data,
args.packageName,
args.input.version,
@@ -172,27 +172,27 @@ class PatcherWorker(
val inputFile = when (val selectedApp = args.input) {
is SelectedApp.Download -> {
runStep(StepId.DownloadAPK, args.onEvent) {
val (plugin, data) = downloaderPluginRepository.unwrapParceledData(
val (downloader, data) = downloaderRepository.unwrapParceledData(
selectedApp.data
)
download(plugin, data)
download(downloader, data)
}
}
is SelectedApp.Search -> {
runStep(StepId.DownloadAPK, args.onEvent) {
downloaderPluginRepository.loadedPluginsFlow.first()
.firstNotNullOfOrNull { plugin ->
downloaderRepository.loadedDownloaderFlow.first()
.firstNotNullOfOrNull { downloader ->
try {
val getScope = object : GetScope {
override val pluginPackageName = plugin.packageName
override val downloaderPackageName = downloader.packageName
override val hostPackageName =
applicationContext.packageName
override suspend fun requestStartActivity(intent: Intent): Intent? {
val result =
args.handleStartActivityRequest(plugin, intent)
args.handleStartActivityRequest(downloader, intent)
return when (result.resultCode) {
Activity.RESULT_OK -> result.data
Activity.RESULT_CANCELED -> throw UserInteractionException.Activity.Cancelled()
@@ -204,7 +204,7 @@ class PatcherWorker(
}
}
withContext(Dispatchers.IO) {
plugin.get(
downloader.get(
getScope,
selectedApp.packageName,
selectedApp.version
@@ -214,7 +214,7 @@ class PatcherWorker(
throw e
} catch (_: UserInteractionException) {
null
}?.let { (data, _) -> download(plugin, data) }
}?.let { (data, _) -> download(downloader, data) }
} ?: throw Exception("App is not available.")
}
}

View File

@@ -90,13 +90,13 @@ fun DashboardScreen(
onAppSelectorClick: () -> Unit,
onSettingsClick: () -> Unit,
onUpdateClick: () -> Unit,
onDownloaderPluginClick: () -> Unit,
onDownloaderClick: () -> Unit,
onAppClick: (String) -> Unit
) {
var selectedSourceCount by rememberSaveable { mutableIntStateOf(0) }
val bundlesSelectable by remember { derivedStateOf { selectedSourceCount > 0 } }
val availablePatches by vm.availablePatches.collectAsStateWithLifecycle(0)
val showNewDownloaderPluginsNotification by vm.newDownloaderPluginsAvailable.collectAsStateWithLifecycle(
val showNewDownloaderNotification by vm.newDownloaderAvailable.collectAsStateWithLifecycle(
false
)
val androidContext = LocalContext.current
@@ -307,14 +307,14 @@ fun DashboardScreen(
)
}
} else null,
if (showNewDownloaderPluginsNotification) {
if (showNewDownloaderNotification) {
{
NotificationCard(
text = stringResource(R.string.new_downloader_plugins_notification),
text = stringResource(R.string.new_downloader_notification),
icon = Icons.Outlined.Download,
modifier = Modifier.clickable(onClick = onDownloaderPluginClick),
modifier = Modifier.clickable(onClick = onDownloaderClick),
actions = {
TextButton(onClick = vm::ignoreNewDownloaderPlugins) {
TextButton(onClick = vm::ignoreNewDownloader) {
Text(stringResource(R.string.dismiss))
}
}

View File

@@ -148,7 +148,7 @@ fun PatcherScreen(
},
title = { Text(title) },
text = {
Text(stringResource(R.string.plugin_activity_dialog_body))
Text(stringResource(R.string.downloader_activity_dialog_body))
}
)
}

View File

@@ -39,7 +39,7 @@ import app.revanced.manager.R
import app.revanced.manager.data.platform.NetworkInfo
import app.revanced.manager.data.room.apps.installed.InstallType
import app.revanced.manager.data.room.apps.installed.InstalledApp
import app.revanced.manager.network.downloader.LoadedDownloaderPlugin
import app.revanced.manager.network.downloader.LoadedDownloader
import app.revanced.manager.ui.component.AlertDialogExtended
import app.revanced.manager.ui.component.AppInfo
import app.revanced.manager.ui.component.AppTopBar
@@ -86,7 +86,7 @@ fun SelectedAppInfoScreen(
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult(),
onResult = vm::handlePluginActivityResult
onResult = vm::handleDownloaderActivityResult
)
EventEffect(flow = vm.launchActivityFlow) { intent ->
launcher.launch(intent)
@@ -140,22 +140,22 @@ fun SelectedAppInfoScreen(
},
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
) { paddingValues ->
val plugins by vm.plugins.collectAsStateWithLifecycle(emptyList())
val downloader by vm.downloader.collectAsStateWithLifecycle(emptyList())
if (vm.showSourceSelector) {
val requiredVersion by vm.requiredVersion.collectAsStateWithLifecycle(null)
AppSourceSelectorDialog(
plugins = plugins,
downloader = downloader,
installedApp = vm.installedAppData,
searchApp = SelectedApp.Search(
vm.packageName,
vm.desiredVersion
),
activeSearchJob = vm.activePluginAction,
activeSearchJob = vm.activeDownloaderAction,
hasRoot = vm.hasRoot,
onDismissRequest = vm::dismissSourceSelector,
onSelectPlugin = vm::searchUsingPlugin,
onSelectDownloader = vm::searchUsingDownloader,
requiredVersion = requiredVersion,
onSelect = {
vm.selectedApp = it
@@ -201,8 +201,8 @@ fun SelectedAppInfoScreen(
is SelectedApp.Installed -> stringResource(R.string.apk_source_installed)
is SelectedApp.Download -> stringResource(
R.string.apk_source_downloader,
plugins.find { it.packageName == app.data.pluginPackageName }?.name
?: app.data.pluginPackageName
downloader.find { it.packageName == app.data.downloaderPackageName }?.name
?: app.data.downloaderPackageName
)
is SelectedApp.Local -> stringResource(R.string.apk_source_local)
@@ -279,14 +279,14 @@ private fun PageItem(@StringRes title: Int, description: String, onClick: () ->
@Composable
private fun AppSourceSelectorDialog(
plugins: List<LoadedDownloaderPlugin>,
downloader: List<LoadedDownloader>,
installedApp: Pair<SelectedApp.Installed, InstalledApp?>?,
searchApp: SelectedApp.Search,
activeSearchJob: String?,
hasRoot: Boolean,
requiredVersion: String?,
onDismissRequest: () -> Unit,
onSelectPlugin: (LoadedDownloaderPlugin) -> Unit,
onSelectDownloader: (LoadedDownloader) -> Unit,
onSelect: (SelectedApp) -> Unit,
) {
val canSelect = activeSearchJob == null
@@ -303,15 +303,15 @@ private fun AppSourceSelectorDialog(
text = {
LazyColumn {
item(key = "auto") {
val hasPlugins = plugins.isNotEmpty()
val hasDownloader = downloader.isNotEmpty()
ListItem(
modifier = Modifier
.clickable(enabled = canSelect && hasPlugins) { onSelect(searchApp) }
.enabled(hasPlugins),
.clickable(enabled = canSelect && hasDownloader) { onSelect(searchApp) }
.enabled(hasDownloader),
headlineContent = { Text(stringResource(R.string.app_source_dialog_option_auto)) },
supportingContent = {
Text(
if (hasPlugins)
if (hasDownloader)
stringResource(R.string.app_source_dialog_option_auto_description)
else
stringResource(R.string.app_source_dialog_option_auto_unavailable)
@@ -349,11 +349,11 @@ private fun AppSourceSelectorDialog(
}
}
items(plugins, key = { "plugin_${it.packageName}" }) { plugin ->
items(downloader, key = { "downloader_${it.packageName}" }) { downloader ->
ListItem(
modifier = Modifier.clickable(enabled = canSelect) { onSelectPlugin(plugin) },
headlineContent = { Text(plugin.name) },
trailingContent = (@Composable { LoadingIndicator() }).takeIf { activeSearchJob == plugin.packageName },
modifier = Modifier.clickable(enabled = canSelect) { onSelectDownloader(downloader) },
headlineContent = { Text(downloader.name) },
trailingContent = (@Composable { LoadingIndicator() }).takeIf { activeSearchJob == downloader.packageName },
colors = transparentListItemColors
)
}

View File

@@ -39,7 +39,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.revanced.manager.R
import app.revanced.manager.network.downloader.DownloaderPluginState
import app.revanced.manager.network.downloader.DownloaderState
import app.revanced.manager.ui.component.AppLabel
import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.ConfirmDialog
@@ -60,7 +60,7 @@ fun DownloadsSettingsScreen(
) {
val context = LocalContext.current
val downloadedApps by viewModel.downloadedApps.collectAsStateWithLifecycle(emptyList())
val pluginStates by viewModel.downloaderPluginStates.collectAsStateWithLifecycle()
val downloaderStates by viewModel.downloaderStates.collectAsStateWithLifecycle()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
var showDeleteConfirmationDialog by rememberSaveable { mutableStateOf(false) }
@@ -68,8 +68,8 @@ fun DownloadsSettingsScreen(
ConfirmDialog(
onDismiss = { showDeleteConfirmationDialog = false },
onConfirm = { viewModel.deleteApps() },
title = stringResource(R.string.downloader_plugin_delete_apps_title),
description = stringResource(R.string.downloader_plugin_delete_apps_description),
title = stringResource(R.string.downloader_delete_apps_title),
description = stringResource(R.string.downloader_delete_apps_description),
icon = Icons.Outlined.Delete
)
}
@@ -92,17 +92,17 @@ fun DownloadsSettingsScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
) { paddingValues ->
PullToRefreshBox(
onRefresh = viewModel::refreshPlugins,
isRefreshing = viewModel.isRefreshingPlugins,
onRefresh = viewModel::refreshDownloader,
isRefreshing = viewModel.isRefreshingDownloader,
modifier = Modifier.padding(paddingValues)
) {
LazyColumnWithScrollbar(
modifier = Modifier.fillMaxSize()
) {
item {
GroupHeader(stringResource(R.string.downloader_plugins))
GroupHeader(stringResource(R.string.downloader))
}
pluginStates.forEach { (packageName, state) ->
downloaderStates.forEach { (packageName, state) ->
item(key = packageName) {
var showDialog by rememberSaveable {
mutableStateOf(false)
@@ -135,39 +135,39 @@ fun DownloadsSettingsScreen(
}
when (state) {
is DownloaderPluginState.Loaded -> TrustDialog(
title = R.string.downloader_plugin_revoke_trust_dialog_title,
is DownloaderState.Loaded -> TrustDialog(
title = R.string.downloader_revoke_trust_dialog_title,
body = stringResource(
R.string.downloader_plugin_trust_dialog_body,
R.string.downloader_trust_dialog_body,
packageName,
signature
),
pluginName = appName,
downloaderName = appName,
signature = signature,
onDismiss = ::dismiss,
onConfirm = {
viewModel.revokePluginTrust(packageName)
viewModel.revokeDownloaderTrust(packageName)
dismiss()
}
)
is DownloaderPluginState.Failed -> ExceptionViewerDialog(
is DownloaderState.Failed -> ExceptionViewerDialog(
text = remember(state.throwable) {
state.throwable.stackTraceToString()
},
onDismiss = ::dismiss
)
is DownloaderPluginState.Untrusted -> TrustDialog(
title = R.string.downloader_plugin_trust_dialog_title,
is DownloaderState.Untrusted -> TrustDialog(
title = R.string.downloader_trust_dialog_title,
body = stringResource(
R.string.downloader_plugin_trust_dialog_body
R.string.downloader_trust_dialog_body
),
pluginName = appName,
downloaderName = appName,
signature = signature,
onDismiss = ::dismiss,
onConfirm = {
viewModel.trustPlugin(packageName)
viewModel.trustDownloader(packageName)
dismiss()
}
)
@@ -184,19 +184,19 @@ fun DownloadsSettingsScreen(
},
supportingContent = stringResource(
when (state) {
is DownloaderPluginState.Loaded -> R.string.downloader_plugin_state_trusted
is DownloaderPluginState.Failed -> R.string.downloader_plugin_state_failed
is DownloaderPluginState.Untrusted -> R.string.downloader_plugin_state_untrusted
is DownloaderState.Loaded -> R.string.downloader_state_trusted
is DownloaderState.Failed -> R.string.downloader_state_failed
is DownloaderState.Untrusted -> R.string.downloader_state_untrusted
}
),
trailingContent = { Text(packageInfo.versionName!!) }
)
}
}
if (pluginStates.isEmpty()) {
if (downloaderStates.isEmpty()) {
item {
Text(
stringResource(R.string.downloader_no_plugins_installed),
stringResource(R.string.no_downloader_installed),
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center
)
@@ -240,7 +240,7 @@ fun DownloadsSettingsScreen(
private fun TrustDialog(
@StringRes title: Int,
body: String,
pluginName: String,
downloaderName: String,
signature: String,
onDismiss: () -> Unit,
onConfirm: () -> Unit
@@ -268,8 +268,8 @@ private fun TrustDialog(
) {
Text(
stringResource(
R.string.downloader_plugin_trust_dialog_plugin,
pluginName
R.string.downloader_trust_dialog_name,
downloaderName
),
)
OutlinedCard(
@@ -279,7 +279,7 @@ private fun TrustDialog(
) {
Text(
stringResource(
R.string.downloader_plugin_trust_dialog_signature,
R.string.downloader_trust_dialog_signature,
signature.chunked(2).joinToString(" ")
), modifier = Modifier.padding(12.dp)
)

View File

@@ -7,7 +7,6 @@ import android.net.Uri
import android.os.Build
import android.os.PowerManager
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.core.content.getSystemService
@@ -15,15 +14,12 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.R
import app.revanced.manager.data.platform.NetworkInfo
import app.revanced.manager.domain.bundles.PatchBundleSource
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.asRemoteOrNull
import app.revanced.manager.domain.bundles.RemotePatchBundle
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.repository.DownloaderPluginRepository
import app.revanced.manager.domain.repository.DownloaderRepository
import app.revanced.manager.domain.repository.PatchBundleRepository
import app.revanced.manager.network.api.ReVancedAPI
import app.revanced.manager.util.PM
import app.revanced.manager.util.toast
import app.revanced.manager.util.uiSafe
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.first
@@ -34,7 +30,7 @@ import kotlinx.coroutines.launch
class DashboardViewModel(
private val app: Application,
private val patchBundleRepository: PatchBundleRepository,
private val downloaderPluginRepository: DownloaderPluginRepository,
private val downloaderRepository: DownloaderRepository,
private val reVancedAPI: ReVancedAPI,
private val networkInfo: NetworkInfo,
val prefs: PreferencesManager,
@@ -45,8 +41,8 @@ class DashboardViewModel(
private val contentResolver: ContentResolver = app.contentResolver
private val powerManager = app.getSystemService<PowerManager>()!!
val newDownloaderPluginsAvailable =
downloaderPluginRepository.newPluginPackageNames.map { it.isNotEmpty() }
val newDownloaderAvailable =
downloaderRepository.newDownloaderPackageNames.map { it.isNotEmpty() }
/**
* Android 11 kills the app process after granting the "install apps" permission, which is a problem for the patcher screen.
@@ -71,8 +67,8 @@ class DashboardViewModel(
}
}
fun ignoreNewDownloaderPlugins() = viewModelScope.launch {
downloaderPluginRepository.acknowledgeAllNewPlugins()
fun ignoreNewDownloader() = viewModelScope.launch {
downloaderRepository.acknowledgeAllNewDownloader()
}
private suspend fun checkForManagerUpdates() {

View File

@@ -1,6 +1,5 @@
package app.revanced.manager.ui.viewmodel
import android.content.pm.PackageInfo
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
@@ -8,7 +7,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.data.room.apps.downloaded.DownloadedApp
import app.revanced.manager.domain.repository.DownloadedAppRepository
import app.revanced.manager.domain.repository.DownloaderPluginRepository
import app.revanced.manager.domain.repository.DownloaderRepository
import app.revanced.manager.util.PM
import app.revanced.manager.util.mutableStateSetOf
import kotlinx.coroutines.Dispatchers
@@ -19,10 +18,10 @@ import kotlinx.coroutines.withContext
class DownloadsViewModel(
private val downloadedAppRepository: DownloadedAppRepository,
private val downloaderPluginRepository: DownloaderPluginRepository,
private val downloaderRepository: DownloaderRepository,
val pm: PM
) : ViewModel() {
val downloaderPluginStates = downloaderPluginRepository.pluginStates
val downloaderStates = downloaderRepository.downloaderStates
val downloadedApps = downloadedAppRepository.getAll().map { downloadedApps ->
downloadedApps.sortedWith(
compareBy<DownloadedApp> {
@@ -32,7 +31,7 @@ class DownloadsViewModel(
}
val appSelection = mutableStateSetOf<DownloadedApp>()
var isRefreshingPlugins by mutableStateOf(false)
var isRefreshingDownloader by mutableStateOf(false)
private set
fun toggleApp(downloadedApp: DownloadedApp) {
@@ -52,17 +51,17 @@ class DownloadsViewModel(
}
}
fun refreshPlugins() = viewModelScope.launch {
isRefreshingPlugins = true
downloaderPluginRepository.reload()
isRefreshingPlugins = false
fun refreshDownloader() = viewModelScope.launch {
isRefreshingDownloader = true
downloaderRepository.reload()
isRefreshingDownloader = false
}
fun trustPlugin(packageName: String) = viewModelScope.launch {
downloaderPluginRepository.trustPackage(packageName)
fun trustDownloader(packageName: String) = viewModelScope.launch {
downloaderRepository.trustPackage(packageName)
}
fun revokePluginTrust(packageName: String) = viewModelScope.launch {
downloaderPluginRepository.revokeTrustForPackage(packageName)
fun revokeDownloaderTrust(packageName: String) = viewModelScope.launch {
downloaderRepository.revokeTrustForPackage(packageName)
}
}

View File

@@ -34,8 +34,8 @@ import app.revanced.manager.patcher.StepId
import app.revanced.manager.patcher.logger.LogLevel
import app.revanced.manager.patcher.logger.Logger
import app.revanced.manager.patcher.worker.PatcherWorker
import app.revanced.manager.plugin.downloader.PluginHostApi
import app.revanced.manager.plugin.downloader.UserInteractionException
import app.revanced.manager.downloader.DownloaderHostApi
import app.revanced.manager.downloader.UserInteractionException
import app.revanced.manager.ui.model.InstallerModel
import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.ui.model.State
@@ -79,7 +79,7 @@ import java.io.File
import java.nio.file.Files
import java.time.Duration
@OptIn(SavedStateHandleSaveableApi::class, PluginHostApi::class)
@OptIn(SavedStateHandleSaveableApi::class, DownloaderHostApi::class)
class PatcherViewModel(
private val input: Patcher.ViewModelParams
) : ViewModel(), KoinComponent, InstallerModel {
@@ -184,13 +184,13 @@ class PatcherViewModel(
input.options,
logger,
setInputFile = { withContext(Dispatchers.Main) { inputFile = it } },
handleStartActivityRequest = { plugin, intent ->
handleStartActivityRequest = { downloader, intent ->
withContext(Dispatchers.Main) {
if (currentActivityRequest != null) throw Exception("Another request is already pending.")
try {
// Wait for the dialog interaction.
val accepted = with(CompletableDeferred<Boolean>()) {
currentActivityRequest = this to plugin.name
currentActivityRequest = this to downloader.name
await()
}

View File

@@ -22,19 +22,19 @@ import app.revanced.manager.R
import app.revanced.manager.data.room.apps.installed.InstalledApp
import app.revanced.manager.domain.installer.RootInstaller
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.repository.DownloaderPluginRepository
import app.revanced.manager.domain.repository.DownloaderRepository
import app.revanced.manager.domain.repository.InstalledAppRepository
import app.revanced.manager.domain.repository.PatchBundleRepository
import app.revanced.manager.domain.repository.PatchOptionsRepository
import app.revanced.manager.domain.repository.PatchSelectionRepository
import app.revanced.manager.patcher.patch.PatchBundleInfo
import app.revanced.manager.patcher.patch.PatchBundleInfo.Extensions.toPatchSelection
import app.revanced.manager.network.downloader.LoadedDownloaderPlugin
import app.revanced.manager.network.downloader.LoadedDownloader
import app.revanced.manager.network.downloader.ParceledDownloaderData
import app.revanced.manager.patcher.patch.PatchBundleInfo.Extensions.requiredOptionsSet
import app.revanced.manager.plugin.downloader.GetScope
import app.revanced.manager.plugin.downloader.PluginHostApi
import app.revanced.manager.plugin.downloader.UserInteractionException
import app.revanced.manager.downloader.GetScope
import app.revanced.manager.downloader.DownloaderHostApi
import app.revanced.manager.downloader.UserInteractionException
import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.ui.model.navigation.Patcher
import app.revanced.manager.ui.model.navigation.SelectedApplicationInfo
@@ -59,7 +59,7 @@ import kotlinx.parcelize.Parcelize
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
@OptIn(SavedStateHandleSaveableApi::class, PluginHostApi::class)
@OptIn(SavedStateHandleSaveableApi::class, DownloaderHostApi::class)
class SelectedAppInfoViewModel(
input: SelectedApplicationInfo.ViewModelParams
) : ViewModel(), KoinComponent {
@@ -67,13 +67,13 @@ class SelectedAppInfoViewModel(
private val bundleRepository: PatchBundleRepository = get()
private val selectionRepository: PatchSelectionRepository = get()
private val optionsRepository: PatchOptionsRepository = get()
private val pluginsRepository: DownloaderPluginRepository = get()
private val downloaderRepository: DownloaderRepository = get()
private val installedAppRepository: InstalledAppRepository = get()
private val rootInstaller: RootInstaller = get()
private val pm: PM = get()
private val savedStateHandle: SavedStateHandle = get()
val prefs: PreferencesManager = get()
val plugins = pluginsRepository.loadedPluginsFlow
val downloader = downloaderRepository.loadedDownloaderFlow
val desiredVersion = input.app.version
val packageName = input.app.packageName
@@ -160,15 +160,15 @@ class SelectedAppInfoViewModel(
var showSourceSelector by mutableStateOf(false)
private set
private var pluginAction: Pair<LoadedDownloaderPlugin, Job>? by mutableStateOf(null)
val activePluginAction get() = pluginAction?.first?.packageName
private var downloaderAction: Pair<LoadedDownloader, Job>? by mutableStateOf(null)
val activeDownloaderAction get() = downloaderAction?.first?.packageName
private var launchedActivity by mutableStateOf<CompletableDeferred<ActivityResult>?>(null)
private val launchActivityChannel = Channel<Intent>()
val launchActivityFlow = launchActivityChannel.receiveAsFlow()
val errorFlow = combine(plugins, snapshotFlow { selectedApp }) { pluginsList, app ->
val errorFlow = combine(downloader, snapshotFlow { selectedApp }) { downloaderList, app ->
when {
app is SelectedApp.Search && pluginsList.isEmpty() -> Error.NoPlugins
app is SelectedApp.Search && downloaderList.isEmpty() -> Error.NoDownloader
else -> null
}
}
@@ -178,23 +178,23 @@ class SelectedAppInfoViewModel(
showSourceSelector = true
}
private fun cancelPluginAction() {
pluginAction?.second?.cancel()
pluginAction = null
private fun cancelDownloaderAction() {
downloaderAction?.second?.cancel()
downloaderAction = null
}
fun dismissSourceSelector() {
cancelPluginAction()
cancelDownloaderAction()
showSourceSelector = false
}
fun searchUsingPlugin(plugin: LoadedDownloaderPlugin) {
cancelPluginAction()
pluginAction = plugin to viewModelScope.launch {
fun searchUsingDownloader(downloader: LoadedDownloader) {
cancelDownloaderAction()
downloaderAction = downloader to viewModelScope.launch {
try {
val scope = object : GetScope {
override val hostPackageName = app.packageName
override val pluginPackageName = plugin.packageName
override val downloaderPackageName = downloader.packageName
override suspend fun requestStartActivity(intent: Intent) =
withContext(Dispatchers.Main) {
if (launchedActivity != null) error("Previous activity has not finished")
@@ -219,7 +219,7 @@ class SelectedAppInfoViewModel(
}
withContext(Dispatchers.IO) {
plugin.get(scope, packageName, desiredVersion)
downloader.get(scope, packageName, desiredVersion)
}?.let { (data, version) ->
if (desiredVersion != null && version != desiredVersion) {
app.toast(app.getString(R.string.downloader_invalid_version))
@@ -228,7 +228,7 @@ class SelectedAppInfoViewModel(
selectedApp = SelectedApp.Download(
packageName,
version,
ParceledDownloaderData(plugin, data)
ParceledDownloaderData(downloader, data)
)
} ?: app.toast(app.getString(R.string.downloader_app_not_found))
} catch (e: UserInteractionException.Activity) {
@@ -239,13 +239,13 @@ class SelectedAppInfoViewModel(
app.toast(app.getString(R.string.downloader_error, e.simpleMessage()))
Log.e(tag, "Downloader.get threw an exception", e)
} finally {
pluginAction = null
downloaderAction = null
dismissSourceSelector()
}
}
}
fun handlePluginActivityResult(result: ActivityResult) {
fun handleDownloaderActivityResult(result: ActivityResult) {
launchedActivity?.complete(result)
}
@@ -307,7 +307,7 @@ class SelectedAppInfoViewModel(
}
enum class Error(@param:StringRes val resourceId: Int) {
NoPlugins(R.string.downloader_no_plugins_available)
NoDownloader(R.string.no_downloader_available)
}
private companion object {

View File

@@ -19,8 +19,8 @@ Second \"item\" text"</string>
<string name="cli">CLI</string>
<string name="manager">Manager</string>
<string name="plugin_host_permission_label">ReVanced Manager plugin host</string>
<string name="plugin_host_permission_description">Used to control access to ReVanced Manager plugins. Only ReVanced Manager has this.</string>
<string name="downloader_host_permission_label">ReVanced Manager downloader host</string>
<string name="downloader_host_permission_description">Used to control access to ReVanced Manager downloader. Only ReVanced Manager has this.</string>
<string name="toast_copied_to_clipboard">Copied!</string>
<string name="copy_to_clipboard">Copy to clipboard</string>
@@ -30,7 +30,7 @@ Second \"item\" text"</string>
<string name="select_app">Select an app</string>
<string name="patches_count_selected">%1$d/%2$d selected</string>
<string name="new_downloader_plugins_notification">New downloader plugins available. Click here to configure them.</string>
<string name="new_downloader_notification">New downloader available. Click here to configure them.</string>
<string name="unsupported_architecture_warning">Patching on this device architecture is unsupported and will most likely fail.</string>
<string name="import_">Import</string>
@@ -56,7 +56,7 @@ Second \"item\" text"</string>
<string name="app_source_dialog_title">Select source</string>
<string name="app_source_dialog_option_auto">Auto</string>
<string name="app_source_dialog_option_auto_description">Use all available downloader to download the app</string>
<string name="app_source_dialog_option_auto_unavailable">No plugins available</string>
<string name="app_source_dialog_option_auto_unavailable">No downloader available</string>
<string name="app_source_dialog_option_installed_no_root">Mounted apps cannot be patched again without root access</string>
<string name="app_source_dialog_option_installed_version_not_suggested">Version %s does not match the suggested version</string>
@@ -87,7 +87,7 @@ Second \"item\" text"</string>
<string name="updates">Updates</string>
<string name="updates_description">Check for updates and view changelogs</string>
<string name="downloads">Downloads</string>
<string name="downloads_description">Downloader plugins and downloaded apps</string>
<string name="downloads_description">Downloader and downloaded apps</string>
<string name="import_export">Import &amp; export</string>
<string name="import_export_description">Keystore, patch options and selection</string>
<string name="advanced">Advanced</string>
@@ -175,17 +175,17 @@ You will not be able to update the previously installed apps from this source."<
<string name="patch_options_reset_all">Reset patch options globally</string>
<string name="patch_options_reset_all_dialog_description">You are about to reset all patch options. You will have to reapply each option again.</string>
<string name="patch_options_reset_all_description">Resets all patch options</string>
<string name="downloader_plugins">Plugins</string>
<string name="downloader_plugin_state_trusted">Trusted</string>
<string name="downloader_plugin_state_failed">Failed to load. Click for more details</string>
<string name="downloader_plugin_state_untrusted">Untrusted</string>
<string name="downloader_plugin_trust_dialog_title">Trust plugin?</string>
<string name="downloader_plugin_revoke_trust_dialog_title">Revoke trust?</string>
<string name="downloader_plugin_trust_dialog_body">Continuing will allow this plugin to run on your system.\n\nOnly enable this plugin if you trust it. Plugins can execute arbitrary code and may compromise your device.</string>
<string name="downloader_plugin_trust_dialog_signature">Signature:\n\n%s</string>
<string name="downloader_plugin_trust_dialog_plugin">Plugin:\n%s</string>
<string name="downloader_plugin_delete_apps_title">Delete selected apps</string>
<string name="downloader_plugin_delete_apps_description">Are you sure you want to delete the selected apps?</string>
<string name="downloader">Downloader</string>
<string name="downloader_state_trusted">Trusted</string>
<string name="downloader_state_failed">Failed to load. Click for more details</string>
<string name="downloader_state_untrusted">Untrusted</string>
<string name="downloader_trust_dialog_title">Trust downloader?</string>
<string name="downloader_revoke_trust_dialog_title">Revoke trust?</string>
<string name="downloader_trust_dialog_body">Continuing will allow this downloader to run on your system.\n\nOnly enable this downloader if you trust it. Downloader can execute arbitrary code and may compromise your device.</string>
<string name="downloader_trust_dialog_signature">Signature:\n\n%s</string>
<string name="downloader_trust_dialog_name">Downloader:\n%s</string>
<string name="downloader_delete_apps_title">Delete selected apps</string>
<string name="downloader_delete_apps_description">Are you sure you want to delete the selected apps?</string>
<string name="downloader_settings_no_apps">No downloaded apps found.</string>
<string name="search_apps">Search apps…</string>
@@ -316,8 +316,8 @@ It is only compatible with the following version(s): %2$s"</string>
<string name="downloader_invalid_version">Downloader did not fetch the correct version</string>
<string name="downloader_app_not_found">Downloader did not find the app</string>
<string name="downloader_error">Downloader error: %s</string>
<string name="downloader_no_plugins_installed">No downloader installed.</string>
<string name="downloader_no_plugins_available">There are downloaders installed but none are trusted. Check your settings.</string>
<string name="no_downloader_installed">No downloader installed.</string>
<string name="no_downloader_available">There are downloader installed but none are trusted. Check your settings.</string>
<string name="already_patched">Already patched</string>
<string name="patch_selector_sheet_filter_title">Filter</string>
@@ -345,7 +345,7 @@ It is only compatible with the following version(s): %2$s"</string>
<string name="save_apk_success">APK Saved</string>
<string name="sign_fail">Failed to sign APK: %s</string>
<string name="save_logs">Save logs</string>
<string name="plugin_activity_dialog_body">User interaction is required in order to proceed with this plugin.</string>
<string name="downloader_activity_dialog_body">User interaction is required in order to proceed with this downloader.</string>
<string name="select_install_type">Select installation type</string>
<string name="patcher_step_group_preparing">Preparing</string>