diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml
index e4a04351f1..4ca4a56580 100644
--- a/.github/workflows/pr-build.yml
+++ b/.github/workflows/pr-build.yml
@@ -23,8 +23,8 @@ jobs:
java-version: '17'
distribution: 'temurin'
- - name: Setup Gradle
- uses: gradle/gradle-build-action@v2
+ - name: Set up Gradle
+ uses: gradle/actions/setup-gradle@v4
- name: Build with Gradle
env:
@@ -38,7 +38,7 @@ jobs:
run: mv app/build/outputs/apk/release/app-release.apk revanced-manager-${{ env.COMMIT_HASH }}.apk
- name: Upload build
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: revanced-manager
path: revanced-manager-${{ env.COMMIT_HASH }}.apk
diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml
index 8e273987cb..c057a644ca 100644
--- a/.github/workflows/release-build.yml
+++ b/.github/workflows/release-build.yml
@@ -20,10 +20,8 @@ jobs:
java-version: '17'
distribution: 'temurin'
- - name: Setup Gradle
- uses: gradle/gradle-build-action@v2
- with:
- cache-disabled: true
+ - name: Set up Gradle
+ uses: gradle/actions/setup-gradle@v4
- name: Build with Gradle
env:
diff --git a/.github/workflows/update-documentation.yml b/.github/workflows/update-documentation.yml
index 77097e2fe6..541a7aa5b5 100644
--- a/.github/workflows/update-documentation.yml
+++ b/.github/workflows/update-documentation.yml
@@ -11,7 +11,7 @@ jobs:
name: Dispatch event to documentation repository
if: github.ref == 'refs/heads/main'
steps:
- - uses: peter-evans/repository-dispatch@v2
+ - uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.DOCUMENTATION_REPO_ACCESS_TOKEN }}
repository: revanced/revanced-documentation
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000000..b8c6fd144b
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Continuing the legacy of Vanced
+
+
+# đź”’ Security Policy
+
+This document describes how to report security vulnerabilities for ReVanced Manager.
+
+## 🚨 Reporting a Vulnerability
+
+Please open an issue in our [advisory tracker](https://github.com/ReVanced/revanced-manager/security/advisories/new) or reach out privately to us on [Discord](https://discord.gg/revanced).
+
+If a vulnerability is confirmed and accepted, you can join our [Discord](https://discord.gg/revanced) server to receive a special contributor role.
+
+### ⏳ Supported Versions
+
+| Version | Branch | Supported |
+| ------- | ------------|------------------- |
+| v1.18.0 | main | :white_check_mark: |
+| latest | dev | :white_check_mark: |
+| latest | compose-dev | :white_check_mark: |
+
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 36daff7468..ee85588414 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,10 +1,12 @@
+import kotlin.random.Random
+
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.devtools)
alias(libs.plugins.about.libraries)
id("kotlin-parcelize")
- kotlin("plugin.serialization") version "1.9.10"
+ kotlin("plugin.serialization") version "1.9.23"
}
android {
@@ -18,16 +20,15 @@ android {
targetSdk = 34
versionCode = 1
versionName = "0.0.1"
- resourceConfigurations.addAll(listOf(
- "en",
- ))
vectorDrawables.useSupportLibrary = true
}
buildTypes {
debug {
applicationIdSuffix = ".debug"
- resValue("string", "app_name", "ReVanced Manager Debug")
+ resValue("string", "app_name", "ReVanced Manager (dev)")
+
+ buildConfigField("long", "BUILD_ID", "${Random.nextLong()}L")
}
release {
@@ -42,6 +43,8 @@ android {
resValue("string", "app_name", "ReVanced Manager Debug")
signingConfig = signingConfigs.getByName("debug")
}
+
+ buildConfigField("long", "BUILD_ID", "0L")
}
}
@@ -54,7 +57,7 @@ android {
includeInApk = false
includeInBundle = false
}
-
+
packaging {
resources.excludes.addAll(listOf(
"/prebuilt/**",
@@ -80,8 +83,21 @@ android {
buildFeatures.compose = true
buildFeatures.aidl = true
+ buildFeatures.buildConfig=true
+
+ android {
+ androidResources {
+ generateLocaleConfig = true
+ }
+ }
- composeOptions.kotlinCompilerExtensionVersion = "1.5.3"
+ composeOptions.kotlinCompilerExtensionVersion = "1.5.10"
+ externalNativeBuild {
+ cmake {
+ path = file("src/main/cpp/CMakeLists.txt")
+ version = "3.22.1"
+ }
+ }
}
kotlin {
@@ -104,14 +120,16 @@ dependencies {
implementation(platform(libs.compose.bom))
implementation(libs.compose.ui)
implementation(libs.compose.ui.preview)
+ implementation(libs.compose.ui.tooling)
implementation(libs.compose.livedata)
implementation(libs.compose.material.icons.extended)
implementation(libs.compose.material3)
// Accompanist
implementation(libs.accompanist.drawablepainter)
- implementation(libs.accompanist.webview)
- implementation(libs.accompanist.placeholder)
+
+ // Placeholder
+ implementation(libs.placeholder.material3)
// HTML Scraper
implementation(libs.skrapeit.dsl)
@@ -135,6 +153,13 @@ dependencies {
implementation(libs.revanced.patcher)
implementation(libs.revanced.library)
+ // Native processes
+ implementation(libs.kotlin.process)
+
+ // HiddenAPI
+ compileOnly(libs.hidden.api.stub)
+
+ // LibSU
implementation(libs.libsu.core)
implementation(libs.libsu.service)
implementation(libs.libsu.nio)
@@ -162,4 +187,13 @@ dependencies {
// Fading Edges
implementation(libs.fading.edges)
+
+ // Scrollbars
+ implementation(libs.scrollbars)
+
+ // Reorderable lists
+ implementation(libs.reorderable)
+
+ // Compose Icons
+ implementation(libs.compose.icons.fontawesome)
}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index f3984a94d6..f284b52a20 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -26,6 +26,10 @@
kotlinx.serialization.KSerializer serializer(...);
}
+# This required for the process runtime.
+-keep class app.revanced.manager.patcher.runtime.process.* {
+ *;
+}
# Required for the patcher to function correctly
-keep class app.revanced.patcher.** {
*;
@@ -45,6 +49,7 @@
-keep class com.android.** {
*;
}
+-dontwarn com.google.auto.value.**
-dontwarn java.awt.**
-dontwarn javax.**
-dontwarn org.slf4j.**
diff --git a/app/schemas/app.revanced.manager.data.room.AppDatabase/1.json b/app/schemas/app.revanced.manager.data.room.AppDatabase/1.json
index 0fb6425d6d..e9c0fd3ae4 100644
--- a/app/schemas/app.revanced.manager.data.room.AppDatabase/1.json
+++ b/app/schemas/app.revanced.manager.data.room.AppDatabase/1.json
@@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 1,
- "identityHash": "802fa2fda94b930bf0ebb85d195f1022",
+ "identityHash": "1dd9d5c0201fdf3cfef3ae669fd65e46",
"entities": [
{
"tableName": "patch_bundles",
@@ -51,17 +51,7 @@
"uid"
]
},
- "indices": [
- {
- "name": "index_patch_bundles_name",
- "unique": true,
- "columnNames": [
- "name"
- ],
- "orders": [],
- "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_patch_bundles_name` ON `${TABLE_NAME}` (`name`)"
- }
- ],
+ "indices": [],
"foreignKeys": []
},
{
@@ -231,7 +221,7 @@
},
{
"tableName": "applied_patch",
- "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `bundle` INTEGER NOT NULL, `patch_name` TEXT NOT NULL, PRIMARY KEY(`package_name`, `bundle`, `patch_name`), FOREIGN KEY(`package_name`) REFERENCES `installed_app`(`current_package_name`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`bundle`) REFERENCES `patch_bundles`(`uid`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `bundle` INTEGER NOT NULL, `patch_name` TEXT NOT NULL, PRIMARY KEY(`package_name`, `bundle`, `patch_name`), FOREIGN KEY(`package_name`) REFERENCES `installed_app`(`current_package_name`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`bundle`) REFERENCES `patch_bundles`(`uid`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "packageName",
@@ -285,7 +275,7 @@
},
{
"table": "patch_bundles",
- "onDelete": "NO ACTION",
+ "onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"bundle"
@@ -407,7 +397,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
- "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '802fa2fda94b930bf0ebb85d195f1022')"
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1dd9d5c0201fdf3cfef3ae669fd65e46')"
]
}
}
\ No newline at end of file
diff --git a/app/src/main/aidl/app/revanced/manager/patcher/runtime/process/IPatcherEvents.aidl b/app/src/main/aidl/app/revanced/manager/patcher/runtime/process/IPatcherEvents.aidl
new file mode 100644
index 0000000000..27a4f61b2a
--- /dev/null
+++ b/app/src/main/aidl/app/revanced/manager/patcher/runtime/process/IPatcherEvents.aidl
@@ -0,0 +1,11 @@
+// IPatcherEvents.aidl
+package app.revanced.manager.patcher.runtime.process;
+
+// Interface for sending events back to the main app process.
+oneway interface IPatcherEvents {
+ void log(String level, String msg);
+ void patchSucceeded();
+ void progress(String name, String state, String msg);
+ // The patching process has ended. The exceptionStackTrace is null if it finished successfully.
+ void finished(String exceptionStackTrace);
+}
\ No newline at end of file
diff --git a/app/src/main/aidl/app/revanced/manager/patcher/runtime/process/IPatcherProcess.aidl b/app/src/main/aidl/app/revanced/manager/patcher/runtime/process/IPatcherProcess.aidl
new file mode 100644
index 0000000000..f938ca6235
--- /dev/null
+++ b/app/src/main/aidl/app/revanced/manager/patcher/runtime/process/IPatcherProcess.aidl
@@ -0,0 +1,14 @@
+// IPatcherProcess.aidl
+package app.revanced.manager.patcher.runtime.process;
+
+import app.revanced.manager.patcher.runtime.process.Parameters;
+import app.revanced.manager.patcher.runtime.process.IPatcherEvents;
+
+interface IPatcherProcess {
+ // Returns BuildConfig.BUILD_ID, which is used to ensure the main app and runner process are running the same code.
+ long buildId();
+ // Makes the patcher process exit with code 0
+ oneway void exit();
+ // Starts patching.
+ oneway void start(in Parameters parameters, IPatcherEvents events);
+}
\ No newline at end of file
diff --git a/app/src/main/aidl/app/revanced/manager/patcher/runtime/process/Parameters.aidl b/app/src/main/aidl/app/revanced/manager/patcher/runtime/process/Parameters.aidl
new file mode 100644
index 0000000000..a1e8bee78d
--- /dev/null
+++ b/app/src/main/aidl/app/revanced/manager/patcher/runtime/process/Parameters.aidl
@@ -0,0 +1,4 @@
+// Parameters.aidl
+package app.revanced.manager.patcher.runtime.process;
+
+parcelable Parameters;
\ No newline at end of file
diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt
new file mode 100644
index 0000000000..64793f8fe5
--- /dev/null
+++ b/app/src/main/cpp/CMakeLists.txt
@@ -0,0 +1,38 @@
+
+# For more information about using CMake with Android Studio, read the
+# documentation: https://d.android.com/studio/projects/add-native-code.html.
+# For more examples on how to use CMake, see https://github.com/android/ndk-samples.
+
+# Sets the minimum CMake version required for this project.
+cmake_minimum_required(VERSION 3.22.1)
+
+# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
+# Since this is the top level CMakeLists.txt, the project name is also accessible
+# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
+# build script scope).
+project("prop_override")
+
+# Creates and names a library, sets it as either STATIC
+# or SHARED, and provides the relative paths to its source code.
+# You can define multiple libraries, and CMake builds them for you.
+# Gradle automatically packages shared libraries with your APK.
+#
+# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
+# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
+# is preferred for the same purpose.
+#
+# In order to load a library into your app from Java/Kotlin, you must call
+# System.loadLibrary() and pass the name of the library defined here;
+# for GameActivity/NativeActivity derived applications, the same library name must be
+# used in the AndroidManifest.xml file.
+add_library(${CMAKE_PROJECT_NAME} SHARED
+ # List C/C++ source files with relative paths to this CMakeLists.txt.
+ prop_override.cpp)
+
+# Specifies libraries CMake should link to your target library. You
+# can link libraries from various origins, such as libraries defined in this
+# build script, prebuilt third-party libraries, or Android system libraries.
+target_link_libraries(${CMAKE_PROJECT_NAME}
+ # List libraries link to the target library
+ android
+ log)
diff --git a/app/src/main/cpp/prop_override.cpp b/app/src/main/cpp/prop_override.cpp
new file mode 100644
index 0000000000..b314ccd117
--- /dev/null
+++ b/app/src/main/cpp/prop_override.cpp
@@ -0,0 +1,62 @@
+// Library for overriding Android system properties via environment variables.
+//
+// Usage: LD_PRELOAD=prop_override.so PROP_dalvik.vm.heapsize=123M getprop dalvik.vm.heapsize
+// Output: 123M
+#include
+#include
+#include
+#include
+
+// Source: https://android.googlesource.com/platform/system/core/+/100b08a848d018eeb1caa5d5e7c7c2aaac65da15/libcutils/include/cutils/properties.h
+#define PROP_VALUE_MAX 92
+// This is the mangled name of "android::base::GetProperty".
+#define GET_PROPERTY_MANGLED_NAME "_ZN7android4base11GetPropertyERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEES9_"
+
+extern "C" typedef int (*property_get_ptr)(const char *, char *, const char *);
+typedef std::string (*GetProperty_ptr)(const std::string &, const std::string &);
+
+char *GetPropOverride(const std::string &key) {
+ auto envKey = "PROP_" + key;
+
+ return getenv(envKey.c_str());
+}
+
+// See: https://android.googlesource.com/platform/system/core/+/100b08a848d018eeb1caa5d5e7c7c2aaac65da15/libcutils/properties.cpp
+extern "C" int property_get(const char *key, char *value, const char *default_value) {
+ auto replacement = GetPropOverride(std::string(key));
+ if (replacement) {
+ int len = strnlen(replacement, PROP_VALUE_MAX);
+
+ strncpy(value, replacement, len);
+ return len;
+ }
+
+ static property_get_ptr original = NULL;
+ if (!original) {
+ // Get the address of the original function.
+ original = reinterpret_cast(dlsym(RTLD_NEXT, "property_get"));
+ }
+
+ return original(key, value, default_value);
+}
+
+// Defining android::base::GetProperty ourselves won't work because std::string has a slightly different "path" in the NDK version of the C++ standard library.
+// We can get around this by forcing the function to adopt a specific name using the asm keyword.
+std::string GetProperty(const std::string &, const std::string &) asm(GET_PROPERTY_MANGLED_NAME);
+
+
+// See: https://android.googlesource.com/platform/system/libbase/+/1a34bb67c4f3ba0a1ea6f4f20ac9fe117ba4fe64/properties.cpp
+// This isn't used for the properties we want to override, but property_get is deprecated so that could change in the future.
+std::string GetProperty(const std::string &key, const std::string &default_value) {
+ auto replacement = GetPropOverride(key);
+ if (replacement) {
+ return std::string(replacement);
+ }
+
+ static GetProperty_ptr original = NULL;
+ if (!original) {
+ original = reinterpret_cast(dlsym(RTLD_NEXT, GET_PROPERTY_MANGLED_NAME));
+ }
+
+ return original(key, default_value);
+}
diff --git a/app/src/main/java/app/revanced/manager/MainActivity.kt b/app/src/main/java/app/revanced/manager/MainActivity.kt
index 5c714a9396..4c8d9ef765 100644
--- a/app/src/main/java/app/revanced/manager/MainActivity.kt
+++ b/app/src/main/java/app/revanced/manager/MainActivity.kt
@@ -5,22 +5,14 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.Update
-import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.Icon
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextButton
import androidx.compose.runtime.getValue
-import androidx.compose.ui.res.stringResource
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
-import app.revanced.manager.ui.component.AutoUpdatesDialog
import app.revanced.manager.ui.destination.Destination
import app.revanced.manager.ui.destination.SettingsDestination
import app.revanced.manager.ui.screen.AppSelectorScreen
import app.revanced.manager.ui.screen.DashboardScreen
import app.revanced.manager.ui.screen.InstalledAppInfoScreen
-import app.revanced.manager.ui.screen.InstallerScreen
+import app.revanced.manager.ui.screen.PatcherScreen
import app.revanced.manager.ui.screen.SelectedAppInfoScreen
import app.revanced.manager.ui.screen.SettingsScreen
import app.revanced.manager.ui.screen.VersionSelectorScreen
@@ -35,7 +27,7 @@ import dev.olshevski.navigation.reimagined.pop
import dev.olshevski.navigation.reimagined.popUpTo
import dev.olshevski.navigation.reimagined.rememberNavController
import org.koin.core.parameter.parametersOf
-import org.koin.androidx.compose.getViewModel as getComposeViewModel
+import org.koin.androidx.compose.koinViewModel as getComposeViewModel
import org.koin.androidx.viewmodel.ext.android.getViewModel as getAndroidViewModel
class MainActivity : ComponentActivity() {
@@ -46,7 +38,6 @@ class MainActivity : ComponentActivity() {
installSplashScreen()
val vm: MainViewModel = getAndroidViewModel()
-
vm.importLegacySettings(this)
setContent {
@@ -59,37 +50,8 @@ class MainActivity : ComponentActivity() {
) {
val navController =
rememberNavController(startDestination = Destination.Dashboard)
-
NavBackHandler(navController)
- val firstLaunch by vm.prefs.firstLaunch.getAsState()
-
- if (firstLaunch) AutoUpdatesDialog(vm::applyAutoUpdatePrefs)
-
- vm.updatedManagerVersion?.let {
- AlertDialog(
- onDismissRequest = vm::dismissUpdateDialog,
- confirmButton = {
- TextButton(
- onClick = {
- vm.dismissUpdateDialog()
- navController.navigate(Destination.Settings(SettingsDestination.Update(false)))
- }
- ) {
- Text(stringResource(R.string.update))
- }
- },
- dismissButton = {
- TextButton(onClick = vm::dismissUpdateDialog) {
- Text(stringResource(R.string.dismiss_temporary))
- }
- },
- icon = { Icon(Icons.Outlined.Update, null) },
- title = { Text(stringResource(R.string.update_available_dialog_title)) },
- text = { Text(stringResource(R.string.update_available_dialog_description, it)) }
- )
- }
-
AnimatedNavHost(
controller = navController
) { destination ->
@@ -97,6 +59,9 @@ class MainActivity : ComponentActivity() {
is Destination.Dashboard -> DashboardScreen(
onSettingsClick = { navController.navigate(Destination.Settings()) },
onAppSelectorClick = { navController.navigate(Destination.AppSelector) },
+ onUpdateClick = { navController.navigate(
+ Destination.Settings(SettingsDestination.Update())
+ ) },
onAppClick = { installedApp ->
navController.navigate(
Destination.InstalledApplicationInfo(
@@ -107,11 +72,11 @@ class MainActivity : ComponentActivity() {
)
is Destination.InstalledApplicationInfo -> InstalledAppInfoScreen(
- onPatchClick = { packageName, patchesSelection ->
+ onPatchClick = { packageName, patchSelection ->
navController.navigate(
Destination.VersionSelector(
packageName,
- patchesSelection
+ patchSelection
)
)
},
@@ -142,14 +107,14 @@ class MainActivity : ComponentActivity() {
navController.navigate(
Destination.SelectedApplicationInfo(
selectedApp,
- destination.patchesSelection,
+ destination.patchSelection,
)
)
},
viewModel = getComposeViewModel {
parametersOf(
destination.packageName,
- destination.patchesSelection
+ destination.patchSelection
)
}
)
@@ -157,7 +122,7 @@ class MainActivity : ComponentActivity() {
is Destination.SelectedApplicationInfo -> SelectedAppInfoScreen(
onPatchClick = { app, patches, options ->
navController.navigate(
- Destination.Installer(
+ Destination.Patcher(
app, patches, options
)
)
@@ -167,13 +132,13 @@ class MainActivity : ComponentActivity() {
parametersOf(
SelectedAppInfoViewModel.Params(
destination.selectedApp,
- destination.patchesSelection
+ destination.patchSelection
)
)
}
)
- is Destination.Installer -> InstallerScreen(
+ is Destination.Patcher -> PatcherScreen(
onBackClick = { navController.popUpTo { it is Destination.Dashboard } },
vm = getComposeViewModel { parametersOf(destination) }
)
diff --git a/app/src/main/java/app/revanced/manager/ManagerApplication.kt b/app/src/main/java/app/revanced/manager/ManagerApplication.kt
index 8a2811bd9c..66ab2483eb 100644
--- a/app/src/main/java/app/revanced/manager/ManagerApplication.kt
+++ b/app/src/main/java/app/revanced/manager/ManagerApplication.kt
@@ -1,23 +1,18 @@
package app.revanced.manager
import android.app.Application
-import android.content.Intent
import app.revanced.manager.di.*
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.repository.PatchBundleRepository
-import app.revanced.manager.service.ManagerRootService
-import app.revanced.manager.service.RootConnection
import kotlinx.coroutines.Dispatchers
import coil.Coil
import coil.ImageLoader
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.internal.BuilderImpl
-import com.topjohnwu.superuser.ipc.RootService
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
-import org.koin.android.ext.android.get
import org.koin.android.ext.android.inject
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
@@ -61,9 +56,6 @@ class ManagerApplication : Application() {
val shellBuilder = BuilderImpl.create().setFlags(Shell.FLAG_MOUNT_MASTER)
Shell.setDefaultBuilder(shellBuilder)
- val intent = Intent(this, ManagerRootService::class.java)
- RootService.bind(intent, get())
-
scope.launch {
prefs.preload()
}
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
index ec01f09ba8..3afbe6e8e5 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
@@ -1,10 +1,11 @@
package app.revanced.manager.data.platform
+import android.Manifest
import android.app.Application
+import android.content.Context
+import android.content.pm.PackageManager
import android.os.Build
import android.os.Environment
-import android.Manifest
-import android.content.pm.PackageManager
import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.result.contract.ActivityResultContracts
import app.revanced.manager.util.RequestManageStorageContract
@@ -16,7 +17,7 @@ class Filesystem(private val app: Application) {
* 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 {
+ val tempDir = app.getDir("ephemeral", Context.MODE_PRIVATE).apply {
deleteRecursively()
mkdirs()
}
diff --git a/app/src/main/java/app/revanced/manager/data/room/Converters.kt b/app/src/main/java/app/revanced/manager/data/room/Converters.kt
index 7de50382f2..a9437f86e2 100644
--- a/app/src/main/java/app/revanced/manager/data/room/Converters.kt
+++ b/app/src/main/java/app/revanced/manager/data/room/Converters.kt
@@ -2,7 +2,7 @@ package app.revanced.manager.data.room
import androidx.room.TypeConverter
import app.revanced.manager.data.room.bundles.Source
-import io.ktor.http.*
+import app.revanced.manager.data.room.options.Option.SerializedValue
import java.io.File
class Converters {
@@ -17,4 +17,10 @@ class Converters {
@TypeConverter
fun fileToString(file: File): String = file.path
+
+ @TypeConverter
+ fun serializedOptionFromString(value: String) = SerializedValue.fromJsonString(value)
+
+ @TypeConverter
+ fun serializedOptionToString(value: SerializedValue) = value.toJsonString()
}
\ No newline at end of file
diff --git a/app/src/main/java/app/revanced/manager/data/room/apps/installed/AppliedPatch.kt b/app/src/main/java/app/revanced/manager/data/room/apps/installed/AppliedPatch.kt
index 6feb04ed49..d2a498a3a0 100644
--- a/app/src/main/java/app/revanced/manager/data/room/apps/installed/AppliedPatch.kt
+++ b/app/src/main/java/app/revanced/manager/data/room/apps/installed/AppliedPatch.kt
@@ -22,7 +22,8 @@ import kotlinx.parcelize.Parcelize
ForeignKey(
PatchBundleEntity::class,
parentColumns = ["uid"],
- childColumns = ["bundle"]
+ childColumns = ["bundle"],
+ onDelete = ForeignKey.CASCADE
)
],
indices = [Index(value = ["bundle"], unique = false)]
diff --git a/app/src/main/java/app/revanced/manager/data/room/apps/installed/InstalledAppDao.kt b/app/src/main/java/app/revanced/manager/data/room/apps/installed/InstalledAppDao.kt
index 90d40b9fbd..c290cc5e9e 100644
--- a/app/src/main/java/app/revanced/manager/data/room/apps/installed/InstalledAppDao.kt
+++ b/app/src/main/java/app/revanced/manager/data/room/apps/installed/InstalledAppDao.kt
@@ -3,7 +3,7 @@ package app.revanced.manager.data.room.apps.installed
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
-import androidx.room.MapInfo
+import androidx.room.MapColumn
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Upsert
@@ -17,12 +17,13 @@ interface InstalledAppDao {
@Query("SELECT * FROM installed_app WHERE current_package_name = :packageName")
suspend fun get(packageName: String): InstalledApp?
- @MapInfo(keyColumn = "bundle", valueColumn = "patch_name")
@Query(
"SELECT bundle, patch_name FROM applied_patch" +
" WHERE package_name = :packageName"
)
- suspend fun getPatchesSelection(packageName: String): Map>
+ suspend fun getPatchesSelection(packageName: String): Map<@MapColumn("bundle") Int, List<@MapColumn(
+ "patch_name"
+ ) String>>
@Transaction
suspend fun upsertApp(installedApp: InstalledApp, appliedPatches: List) {
diff --git a/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleDao.kt b/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleDao.kt
index 28f54e5c07..77de9b0311 100644
--- a/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleDao.kt
+++ b/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleDao.kt
@@ -9,7 +9,7 @@ interface PatchBundleDao {
suspend fun all(): List
@Query("SELECT version, integrations_version, auto_update FROM patch_bundles WHERE uid = :uid")
- fun getPropsById(uid: Int): Flow
+ fun getPropsById(uid: Int): Flow
@Query("UPDATE patch_bundles SET version = :patches, integrations_version = :integrations WHERE uid = :uid")
suspend fun updateVersion(uid: Int, patches: String?, integrations: String?)
@@ -17,6 +17,9 @@ interface PatchBundleDao {
@Query("UPDATE patch_bundles SET auto_update = :value WHERE uid = :uid")
suspend fun setAutoUpdate(uid: Int, value: Boolean)
+ @Query("UPDATE patch_bundles SET name = :value WHERE uid = :uid")
+ suspend fun setName(uid: Int, value: String)
+
@Query("DELETE FROM patch_bundles WHERE uid != 0")
suspend fun purgeCustomBundles()
diff --git a/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleEntity.kt b/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleEntity.kt
index e9869de9f4..d120abf5b9 100644
--- a/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleEntity.kt
+++ b/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleEntity.kt
@@ -21,7 +21,7 @@ sealed class Source {
}
companion object {
- fun from(value: String) = when(value) {
+ fun from(value: String) = when (value) {
Local.SENTINEL -> Local
API.SENTINEL -> API
else -> Remote(Url(value))
@@ -34,7 +34,7 @@ data class VersionInfo(
@ColumnInfo(name = "integrations_version") val integrations: String? = null,
)
-@Entity(tableName = "patch_bundles", indices = [Index(value = ["name"], unique = true)])
+@Entity(tableName = "patch_bundles")
data class PatchBundleEntity(
@PrimaryKey val uid: Int,
@ColumnInfo(name = "name") val name: String,
diff --git a/app/src/main/java/app/revanced/manager/data/room/options/Option.kt b/app/src/main/java/app/revanced/manager/data/room/options/Option.kt
index 3a70a9a56e..b59dbd165d 100644
--- a/app/src/main/java/app/revanced/manager/data/room/options/Option.kt
+++ b/app/src/main/java/app/revanced/manager/data/room/options/Option.kt
@@ -3,6 +3,23 @@ package app.revanced.manager.data.room.options
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
+import app.revanced.manager.patcher.patch.Option
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.SerializationException
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonNull
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.add
+import kotlinx.serialization.json.boolean
+import kotlinx.serialization.json.buildJsonArray
+import kotlinx.serialization.json.float
+import kotlinx.serialization.json.int
+import kotlinx.serialization.json.jsonArray
+import kotlinx.serialization.json.jsonPrimitive
+import kotlinx.serialization.json.long
+import kotlin.reflect.KClass
@Entity(
tableName = "options",
@@ -19,5 +36,74 @@ data class Option(
@ColumnInfo(name = "patch_name") val patchName: String,
@ColumnInfo(name = "key") val key: String,
// Encoded as Json.
- @ColumnInfo(name = "value") val value: String,
-)
\ No newline at end of file
+ @ColumnInfo(name = "value") val value: SerializedValue,
+) {
+ @Serializable
+ data class SerializedValue(val raw: JsonElement) {
+ fun toJsonString() = json.encodeToString(raw)
+ fun deserializeFor(option: Option<*>): Any? {
+ if (raw is JsonNull) return null
+
+ val errorMessage = "Cannot deserialize value as ${option.type}"
+ try {
+ if (option.type.endsWith("Array")) {
+ val elementType = option.type.removeSuffix("Array")
+ return raw.jsonArray.map { deserializeBasicType(elementType, it.jsonPrimitive) }
+ }
+
+ return deserializeBasicType(option.type, raw.jsonPrimitive)
+ } catch (e: IllegalArgumentException) {
+ throw SerializationException(errorMessage, e)
+ } catch (e: IllegalStateException) {
+ throw SerializationException(errorMessage, e)
+ } catch (e: kotlinx.serialization.SerializationException) {
+ throw SerializationException(errorMessage, e)
+ }
+ }
+
+ companion object {
+ private val json = Json {
+ // Patcher does not forbid the use of these values, so we should support them.
+ allowSpecialFloatingPointValues = true
+ }
+
+ private fun deserializeBasicType(type: String, value: JsonPrimitive) = when (type) {
+ "Boolean" -> value.boolean
+ "Int" -> value.int
+ "Long" -> value.long
+ "Float" -> value.float
+ "String" -> value.content.also { if (!value.isString) throw SerializationException("Expected value to be a string: $value") }
+ else -> throw SerializationException("Unknown type: $type")
+ }
+
+ fun fromJsonString(value: String) = SerializedValue(json.decodeFromString(value))
+ fun fromValue(value: Any?) = SerializedValue(when (value) {
+ null -> JsonNull
+ is Number -> JsonPrimitive(value)
+ is Boolean -> JsonPrimitive(value)
+ is String -> JsonPrimitive(value)
+ is List<*> -> buildJsonArray {
+ var elementClass: KClass? = null
+
+ value.forEach {
+ when (it) {
+ null -> throw SerializationException("List elements must not be null")
+ is Number -> add(it)
+ is Boolean -> add(it)
+ is String -> add(it)
+ else -> throw SerializationException("Unknown element type: ${it::class.simpleName}")
+ }
+
+ if (elementClass == null) elementClass = it::class
+ else if (elementClass != it::class) throw SerializationException("List elements must have the same type")
+ }
+ }
+
+ else -> throw SerializationException("Unknown type: ${value::class.simpleName}")
+ })
+ }
+ }
+
+ class SerializationException(message: String, cause: Throwable? = null) :
+ Exception(message, cause)
+}
diff --git a/app/src/main/java/app/revanced/manager/data/room/options/OptionDao.kt b/app/src/main/java/app/revanced/manager/data/room/options/OptionDao.kt
index fa343a6db6..5a147f6f3b 100644
--- a/app/src/main/java/app/revanced/manager/data/room/options/OptionDao.kt
+++ b/app/src/main/java/app/revanced/manager/data/room/options/OptionDao.kt
@@ -2,7 +2,7 @@ package app.revanced.manager.data.room.options
import androidx.room.Dao
import androidx.room.Insert
-import androidx.room.MapInfo
+import androidx.room.MapColumn
import androidx.room.Query
import androidx.room.Transaction
import kotlinx.coroutines.flow.Flow
@@ -10,13 +10,12 @@ import kotlinx.coroutines.flow.Flow
@Dao
abstract class OptionDao {
@Transaction
- @MapInfo(keyColumn = "patch_bundle")
@Query(
"SELECT patch_bundle, `group`, patch_name, `key`, value FROM option_groups" +
" LEFT JOIN options ON uid = options.`group`" +
" WHERE package_name = :packageName"
)
- abstract suspend fun getOptions(packageName: String): Map>
+ abstract suspend fun getOptions(packageName: String): Map<@MapColumn("patch_bundle") Int, List