Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions app/src/main/java/app/revanced/manager/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -299,9 +299,12 @@ private fun ReVancedManager(vm: MainViewModel) {
composable<SelectedApplicationInfo.PatchesSelector> {
val data =
it.getComplexArg<SelectedApplicationInfo.PatchesSelector.ViewModelParams>()
val parentBackStackEntry = navController.navGraphEntry(it)
val parentData =
parentBackStackEntry.getComplexArg<SelectedApplicationInfo.ViewModelParams>()
val selectedAppInfoVm = koinViewModel<SelectedAppInfoViewModel>(
viewModelStoreOwner = navController.navGraphEntry(it)
)
viewModelStoreOwner = parentBackStackEntry
) { parametersOf(parentData) }

PatchesSelectorScreen(
onBackClick = navController::popBackStackSafe,
Expand All @@ -319,9 +322,12 @@ private fun ReVancedManager(vm: MainViewModel) {
composable<SelectedApplicationInfo.RequiredOptions> {
val data =
it.getComplexArg<SelectedApplicationInfo.PatchesSelector.ViewModelParams>()
val parentBackStackEntry = navController.navGraphEntry(it)
val parentData =
parentBackStackEntry.getComplexArg<SelectedApplicationInfo.ViewModelParams>()
val selectedAppInfoVm = koinViewModel<SelectedAppInfoViewModel>(
viewModelStoreOwner = navController.navGraphEntry(it)
)
viewModelStoreOwner = parentBackStackEntry
) { parametersOf(parentData) }

RequiredOptionsScreen(
onBackClick = navController::popBackStackSafe,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import app.revanced.manager.data.room.downloader.DownloaderEntity
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 kotlin.random.Random
import java.util.UUID

@Database(
entities = [PatchBundleEntity::class, PatchSelection::class, SelectedPatch::class, DownloadedApp::class, InstalledApp::class, AppliedPatch::class, InstalledPatchBundle::class, OptionGroup::class, Option::class, DownloaderEntity::class],
Expand Down Expand Up @@ -51,6 +51,6 @@ abstract class AppDatabase : RoomDatabase() {
class DeleteTrustedDownloaders : AutoMigrationSpec

companion object {
fun generateUid() = Random.Default.nextInt()
fun generateUid() = UUID.randomUUID().mostSignificantBits.toInt()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package app.revanced.manager.data.room.options
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.MapColumn
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import app.revanced.manager.data.room.AppDatabase
import kotlinx.coroutines.flow.Flow

@Dao
Expand All @@ -23,8 +25,8 @@ abstract class OptionDao {
@Query("SELECT package_name FROM option_groups")
abstract fun getPackagesWithOptions(): Flow<List<String>>

@Insert
abstract suspend fun createOptionGroup(group: OptionGroup)
@Insert(onConflict = OnConflictStrategy.IGNORE)
protected abstract suspend fun createOptionGroupIfMissing(group: OptionGroup)

@Query("DELETE FROM option_groups WHERE patch_bundle = :uid")
abstract suspend fun resetOptionsForPatchBundle(uid: Int)
Expand All @@ -47,4 +49,18 @@ abstract class OptionDao {
clearGroup(groupId)
insertOptions(options)
}

@Transaction
open suspend fun getOrCreateGroupId(bundleUid: Int, packageName: String): Int {
getGroupId(bundleUid, packageName)?.let { return it }
createOptionGroupIfMissing(
OptionGroup(
uid = AppDatabase.generateUid(),
patchBundle = bundleUid,
packageName = packageName
)
)
return getGroupId(bundleUid, packageName)
?: throw IllegalStateException("Failed to create options group for $packageName")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package app.revanced.manager.data.room.selection
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.MapColumn
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import app.revanced.manager.data.room.AppDatabase
import kotlinx.coroutines.flow.Flow

@Dao
Expand Down Expand Up @@ -32,8 +34,8 @@ abstract class SelectionDao {
@Query("SELECT uid FROM patch_selections WHERE patch_bundle = :bundleUid AND package_name = :packageName")
abstract suspend fun getSelectionId(bundleUid: Int, packageName: String): Int?

@Insert
abstract suspend fun createSelection(selection: PatchSelection)
@Insert(onConflict = OnConflictStrategy.IGNORE)
protected abstract suspend fun createSelectionIfMissing(selection: PatchSelection)

@Query("SELECT package_name FROM patch_selections")
abstract fun getPackagesWithSelection(): Flow<List<String>>
Expand Down Expand Up @@ -65,4 +67,28 @@ abstract class SelectionDao {
clearSelection(selectionUid)
selectPatches(patches.map { SelectedPatch(selectionUid, it) })
}

@Transaction
open suspend fun getOrCreateSelectionId(bundleUid: Int, packageName: String): Int {
getSelectionId(bundleUid, packageName)?.let { return it }
createSelectionIfMissing(
PatchSelection(
uid = AppDatabase.generateUid(),
patchBundle = bundleUid,
packageName = packageName
)
)
return getSelectionId(bundleUid, packageName)
?: throw IllegalStateException("Failed to create selection for $packageName")
}

@Transaction
open suspend fun replaceForPatchBundle(bundleUid: Int, selections: Map<String, Set<String>>) {
resetForPatchBundle(bundleUid)
selections.forEach { (packageName, patches) ->
val selectionUid = getOrCreateSelectionId(bundleUid, packageName)
clearSelection(selectionUid)
selectPatches(patches.map { SelectedPatch(selectionUid, it) })
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import android.os.Process
import app.revanced.manager.IRootSystemService
import app.revanced.manager.service.ManagerRootService
import app.revanced.manager.util.PM
Expand Down Expand Up @@ -71,7 +72,8 @@ class RootInstaller(

suspend fun isAppMounted(packageName: String) = withContext(Dispatchers.IO) {
pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir?.let {
execute("mount | grep \"$it\"").isSuccess
// Use -F to ensure the path isn't treated like a regex
execute("mount | grep -F \"$it\"").isSuccess
} ?: false
}

Expand Down Expand Up @@ -112,10 +114,11 @@ class RootInstaller(
unmount(packageName)

stockAPK?.let { stockApp ->
// TODO: get user id programmatically
execute("pm uninstall -k --user 0 $packageName")
// Android assigns 100000 UIDs per user (https://android.googlesource.com/platform/frameworks/base/+/HEAD/core/java/android/os/UserHandle.java#47)
val userId = Process.myUid() / 100000
execute("pm uninstall -k --user ${userId} \"$packageName\"")

execute("pm install -r -d --user 0 \"${stockApp.absolutePath}\"")
execute("pm install -r -d --user ${userId} \"${stockApp.absolutePath}\"")
.assertSuccess("Failed to install stock app")

stockApp.delete()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class KeystoreManager(app: Application, private val prefs: PreferencesManager) {
* Default alias and password for the keystore.
*/
const val DEFAULT = "ReVanced"
private val eightYearsFromNow get() = Date(System.currentTimeMillis() + (365.days * 8).inWholeMilliseconds * 24)
private val eightYearsFromNow get() = Date(System.currentTimeMillis() + (365.days * 8).inWholeMilliseconds)
}

private val keystorePath =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,10 @@ class LongSetPreference(
) : Preference<Set<Long>>(dataStore, default) {
private val key = stringSetPreferencesKey(key)

override fun Preferences.read() = this[key]?.mapTo(mutableSetOf()) { it.toLong() } ?: default
override fun Preferences.read() = this[key]?.mapNotNullTo(mutableSetOf()) {
it.toLongOrNull()
} ?: default

override fun MutablePreferences.write(value: Set<Long>) {
this[key] = value.mapTo(mutableSetOf()) { it.toString() }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ class DownloadedAppRepository(
db: AppDatabase,
private val pm: PM
) {
companion object {
private const val CACHE_TTL_MS = 6 * 60 * 60 * 1_000L
}

private val dir = app.getDir("downloaded-apps", Context.MODE_PRIVATE)
private val dao = db.downloadedAppDao()

Expand All @@ -39,11 +43,10 @@ class DownloadedAppRepository(
private fun getApkFileForDir(directory: File) = directory.listFiles()!!.first()

suspend fun cleanUp() {
val threshold = 1000 * 60 * 60 * 6
val now = System.currentTimeMillis()

val targets = getAll().first().filter {
(now - it.lastUsed) > threshold
(now - it.lastUsed) > CACHE_TTL_MS
}
delete(targets)
}
Expand Down Expand Up @@ -98,9 +101,7 @@ class DownloadedAppRepository(

override fun write(b: ByteArray?, off: Int, len: Int) =
out.write(b, off, len).also {
emitProgress(
(len - off).toLong()
)
emitProgress(len.toLong())
}
}
downloader.impl.download(scope, data, stream)
Expand All @@ -114,23 +115,24 @@ class DownloadedAppRepository(
val pkgInfo =
pm.getPackageInfo(targetFile.toFile()) ?: error("Downloaded APK file is invalid")
if (pkgInfo.packageName != expectedPackageName) error("Downloaded APK has the wrong package name. Expected: $expectedPackageName, Actual: ${pkgInfo.packageName}")
val versionName = pkgInfo.versionName ?: error("Downloaded APK has no version name")
expectedVersion?.let {
if (
pkgInfo.versionName != expectedVersion &&
versionName != expectedVersion &&
(appCompatibilityCheck || patchesCompatibilityCheck)
) error("The selected app version ($pkgInfo.versionName) doesn't match the suggested version. Please use the suggested version ($expectedVersion), or adjust your settings by disabling \"Require suggested app version\" and enabling \"Disable version compatibility check\".")
) error("The selected app version ($versionName) doesn't match the suggested version. Please use the suggested version ($expectedVersion), or adjust your settings by disabling \"Require suggested app version\" and enabling \"Disable version compatibility check\".")
}

// Delete the previous copy (if present).
dao.get(pkgInfo.packageName, pkgInfo.versionName!!)?.directory?.let {
dao.get(pkgInfo.packageName, versionName)?.directory?.let {
if (!dir.resolve(it)
.deleteRecursively()
) throw Exception("Failed to delete existing directory")
}
dao.upsert(
DownloadedApp(
packageName = pkgInfo.packageName,
version = pkgInfo.versionName!!,
version = versionName,
directory = relativePath,
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package app.revanced.manager.domain.repository
import android.util.Log
import app.revanced.manager.data.room.AppDatabase
import app.revanced.manager.data.room.options.Option
import app.revanced.manager.data.room.options.OptionGroup
import app.revanced.manager.patcher.patch.PatchInfo
import app.revanced.manager.util.Options
import app.revanced.manager.util.tag
Expand All @@ -13,13 +12,6 @@ import kotlinx.coroutines.flow.map
class PatchOptionsRepository(db: AppDatabase) {
private val dao = db.optionDao()

private suspend fun getOrCreateGroup(bundleUid: Int, packageName: String) =
dao.getGroupId(bundleUid, packageName) ?: OptionGroup(
uid = AppDatabase.generateUid(),
patchBundle = bundleUid,
packageName = packageName
).also { dao.createOptionGroup(it) }.uid

suspend fun getOptions(
packageName: String,
bundlePatches: Map<Int, Map<String, PatchInfo>>
Expand Down Expand Up @@ -57,7 +49,7 @@ class PatchOptionsRepository(db: AppDatabase) {

suspend fun saveOptions(packageName: String, options: Options) =
dao.updateOptions(options.entries.associate { (sourceUid, bundlePatchOptions) ->
val groupId = getOrCreateGroup(sourceUid, packageName)
val groupId = dao.getOrCreateGroupId(sourceUid, packageName)

groupId to bundlePatchOptions.flatMap { (patchName, patchOptions) ->
patchOptions.mapNotNull { (key, value) ->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,18 @@
package app.revanced.manager.domain.repository

import app.revanced.manager.data.room.AppDatabase
import app.revanced.manager.data.room.AppDatabase.Companion.generateUid
import app.revanced.manager.data.room.selection.PatchSelection
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map

class PatchSelectionRepository(db: AppDatabase) {
private val dao = db.selectionDao()

private suspend fun getOrCreateSelection(bundleUid: Int, packageName: String) =
dao.getSelectionId(bundleUid, packageName) ?: PatchSelection(
uid = generateUid(),
patchBundle = bundleUid,
packageName = packageName
).also { dao.createSelection(it) }.uid

suspend fun getSelection(packageName: String): Map<Int, Set<String>> =
dao.getSelectedPatches(packageName).mapValues { it.value.toSet() }

suspend fun updateSelection(packageName: String, selection: Map<Int, Set<String>>) =
dao.updateSelections(selection.mapKeys { (sourceUid, _) ->
getOrCreateSelection(
dao.getOrCreateSelectionId(
sourceUid,
packageName
)
Expand All @@ -47,10 +38,10 @@ class PatchSelectionRepository(db: AppDatabase) {
suspend fun export(bundleUid: Int): SerializedSelection = dao.exportSelection(bundleUid)

suspend fun import(bundleUid: Int, selection: SerializedSelection) {
dao.resetForPatchBundle(bundleUid)
dao.updateSelections(selection.entries.associate { (packageName, patches) ->
getOrCreateSelection(bundleUid, packageName) to patches.toSet()
})
dao.replaceForPatchBundle(
bundleUid,
selection.mapValues { (_, patches) -> patches.toSet() }
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.work.OneTimeWorkRequest
import androidx.work.OutOfQuotaPolicy
import androidx.work.WorkManager
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap

class WorkerRepository(app: Application) {
val workManager = WorkManager.getInstance(app)
Expand All @@ -14,7 +15,7 @@ class WorkerRepository(app: Application) {
* The standard WorkManager communication APIs use [androidx.work.Data], which has too many limitations.
* We can get around those limits by passing inputs using global variables instead.
*/
val workerInputs = mutableMapOf<UUID, Any>()
val workerInputs = ConcurrentHashMap<UUID, Any>()

@Suppress("UNCHECKED_CAST")
fun <A : Any, W : Worker<A>> claimInput(worker: W): A {
Expand All @@ -30,7 +31,7 @@ class WorkerRepository(app: Application) {
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build()
workerInputs[request.id] = input
workManager.enqueueUniqueWork(name, ExistingWorkPolicy.REPLACE, request)
workManager.enqueueUniqueWork(name, ExistingWorkPolicy.KEEP, request)
return request.id
}
}
Loading