chore: Migrate to compose-dev branch
13
android/.gitignore
vendored
@@ -1,13 +0,0 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/to/reference-keystore
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
@@ -1,104 +0,0 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.revanced.manager.flutter"
|
||||
compileSdk = 35
|
||||
|
||||
compileOptions {
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "app.revanced.manager.flutter"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
|
||||
resValue("string", "app_name", "ReVanced Manager")
|
||||
}
|
||||
|
||||
applicationVariants.all {
|
||||
outputs.all {
|
||||
this as com.android.build.gradle.internal.api.ApkVariantOutputImpl
|
||||
|
||||
outputFileName = "revanced-manager-$versionName.apk"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
configureEach {
|
||||
isShrinkResources = false
|
||||
isMinifyEnabled = false
|
||||
|
||||
signingConfig = signingConfigs["debug"]
|
||||
|
||||
ndk.abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86_64")
|
||||
}
|
||||
|
||||
release {
|
||||
isShrinkResources = true
|
||||
isMinifyEnabled = true
|
||||
|
||||
val keystoreFile = file("keystore.jks")
|
||||
if (keystoreFile.exists()) {
|
||||
signingConfig = signingConfigs.create("release") {
|
||||
storeFile = keystoreFile
|
||||
storePassword = System.getenv("KEYSTORE_PASSWORD")
|
||||
keyAlias = System.getenv("KEYSTORE_ENTRY_ALIAS")
|
||||
keyPassword = System.getenv("KEYSTORE_ENTRY_PASSWORD")
|
||||
}
|
||||
|
||||
resValue("string", "app_name", "ReVanced Manager")
|
||||
} else {
|
||||
applicationIdSuffix = ".development"
|
||||
resValue("string", "app_name", "ReVanced Manager (Development)")
|
||||
signingConfig = signingConfigs["debug"]
|
||||
}
|
||||
}
|
||||
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
resValue("string", "app_name", "ReVanced Manager (Debug)")
|
||||
}
|
||||
|
||||
named("profile") {
|
||||
initWith(getByName("debug"))
|
||||
applicationIdSuffix = ".profile"
|
||||
resValue("string", "app_name", "ReVanced Manager (Profile)")
|
||||
}
|
||||
}
|
||||
|
||||
packaging {
|
||||
jniLibs {
|
||||
useLegacyPackaging = true
|
||||
excludes.add("/prebuilt/**")
|
||||
}
|
||||
|
||||
resources {
|
||||
excludes.add("/prebuilt/**")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring(libs.desugar.jdk.libs) // https://pub.dev/packages/flutter_local_notifications#gradle-setup
|
||||
implementation(libs.revanced.patcher)
|
||||
implementation(libs.revanced.library)
|
||||
}
|
||||
17
android/app/proguard-rules.pro
vendored
@@ -1,17 +0,0 @@
|
||||
-dontobfuscate
|
||||
|
||||
-keep class app.revanced.** { *; }
|
||||
-keep class com.android.tools.smali.** { *; }
|
||||
-keep class kotlin.** { *; }
|
||||
-keep class com.google.auto.value.** { *; }
|
||||
-keep class com.android.apksig.internal.** { *; }
|
||||
-keepnames class com.google.common.collect.**
|
||||
-keepnames class org.xmlpull.** { *; }
|
||||
|
||||
-dontwarn com.google.auto.value.**
|
||||
-dontwarn com.google.j2objc.annotations.*
|
||||
-dontwarn java.awt.**
|
||||
-dontwarn javax.**
|
||||
|
||||
# Required for Share Plus, ref: ReVanced/revanced-manager#2474
|
||||
-keep interface android.content.res.XmlResourceParser { *; }
|
||||
@@ -1,7 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -1,88 +0,0 @@
|
||||
<manifest xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
|
||||
<application
|
||||
android:label="@string/app_name"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:largeHeap="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:enableOnBackInvokedCallback="true">
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.EnableImpeller"
|
||||
android:value="false" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<service
|
||||
android:name="de.julianassmann.flutter_background.IsolateHolderService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="shortService" />
|
||||
<activity
|
||||
android:name=".ExportSettingsActivity"
|
||||
android:exported="true">
|
||||
</activity>
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileProvider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
<receiver
|
||||
android:name=".utils.packageInstaller.InstallerReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="APP_INSTALL_ACTION" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".utils.packageInstaller.UninstallerReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="APP_UNINSTALL_ACTION" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -1,81 +0,0 @@
|
||||
package app.revanced.manager.flutter
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.util.Base64
|
||||
import org.json.JSONObject
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.security.MessageDigest
|
||||
|
||||
class ExportSettingsActivity : Activity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (getFingerprint(callingPackage!!) == getFingerprint(packageName)) {
|
||||
// Create JSON Object
|
||||
val json = JSONObject()
|
||||
|
||||
// Default Data
|
||||
json.put("keystorePassword", "s3cur3p@ssw0rd")
|
||||
|
||||
// Load Shared Preferences
|
||||
val sharedPreferences = getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE)
|
||||
val allEntries: Map<String, *> = sharedPreferences.getAll()
|
||||
for ((key, value) in allEntries.entries) {
|
||||
json.put(key.replace("flutter.", ""), value)
|
||||
}
|
||||
|
||||
// Load keystore
|
||||
val keystoreFile = File(getExternalFilesDir(null), "/revanced-manager.keystore")
|
||||
if (keystoreFile.exists()) {
|
||||
val keystoreBytes = keystoreFile.readBytes()
|
||||
val keystoreBase64 = Base64.encodeToString(keystoreBytes, Base64.DEFAULT)
|
||||
json.put("keystore", keystoreBase64)
|
||||
}
|
||||
|
||||
// Load saved patches
|
||||
val storedPatchesFile = File(filesDir.parentFile.absolutePath, "/app_flutter/selected-patches.json")
|
||||
if (storedPatchesFile.exists()) {
|
||||
val patchesBytes = storedPatchesFile.readBytes()
|
||||
val patches = String(patchesBytes, Charsets.UTF_8)
|
||||
json.put("patches", JSONObject(patches))
|
||||
}
|
||||
|
||||
// Send data back
|
||||
val resultIntent = Intent()
|
||||
resultIntent.putExtra("data", json.toString())
|
||||
setResult(Activity.RESULT_OK, resultIntent)
|
||||
finish()
|
||||
} else {
|
||||
val resultIntent = Intent()
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
fun getFingerprint(packageName: String): String {
|
||||
// Get the signature of the app that matches the package name
|
||||
val packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
|
||||
val signature = packageInfo.signatures!![0]
|
||||
|
||||
// Get the raw certificate data
|
||||
val rawCert = signature.toByteArray()
|
||||
|
||||
// Generate an X509Certificate from the data
|
||||
val certFactory = CertificateFactory.getInstance("X509")
|
||||
val x509Cert = certFactory.generateCertificate(ByteArrayInputStream(rawCert)) as X509Certificate
|
||||
|
||||
// Get the SHA256 fingerprint
|
||||
val fingerprint = MessageDigest.getInstance("SHA256").digest(x509Cert.encoded).joinToString("") {
|
||||
"%02x".format(it)
|
||||
}
|
||||
|
||||
return fingerprint
|
||||
}
|
||||
}
|
||||
@@ -1,427 +0,0 @@
|
||||
package app.revanced.manager.flutter
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.app.SearchManager
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import app.revanced.library.ApkUtils
|
||||
import app.revanced.library.ApkUtils.applyTo
|
||||
import app.revanced.manager.flutter.utils.Aapt
|
||||
import app.revanced.manager.flutter.utils.packageInstaller.InstallerReceiver
|
||||
import app.revanced.manager.flutter.utils.packageInstaller.UninstallerReceiver
|
||||
import app.revanced.patcher.Patcher
|
||||
import app.revanced.patcher.PatcherConfig
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.PatchResult
|
||||
import app.revanced.patcher.patch.loadPatchesFromDex
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.util.logging.LogRecord
|
||||
import java.util.logging.Logger
|
||||
|
||||
|
||||
class MainActivity : FlutterActivity() {
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private lateinit var installerChannel: MethodChannel
|
||||
private var cancel: Boolean = false
|
||||
private var stopResult: MethodChannel.Result? = null
|
||||
|
||||
private lateinit var patches: Set<Patch<*>>
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
|
||||
val patcherChannel = "app.revanced.manager.flutter/patcher"
|
||||
val installerChannel = "app.revanced.manager.flutter/installer"
|
||||
val openBrowserChannel = "app.revanced.manager.flutter/browser"
|
||||
|
||||
MethodChannel(
|
||||
flutterEngine.dartExecutor.binaryMessenger,
|
||||
openBrowserChannel
|
||||
).setMethodCallHandler { call, result ->
|
||||
if (call.method == "openBrowser") {
|
||||
val searchQuery = call.argument<String>("query")
|
||||
openBrowser(searchQuery)
|
||||
result.success(null)
|
||||
} else {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
val mainChannel =
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, patcherChannel)
|
||||
|
||||
this.installerChannel =
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, installerChannel)
|
||||
|
||||
mainChannel.setMethodCallHandler { call, result ->
|
||||
when (call.method) {
|
||||
"runPatcher" -> {
|
||||
val inFilePath = call.argument<String>("inFilePath")
|
||||
val outFilePath = call.argument<String>("outFilePath")
|
||||
val selectedPatches = call.argument<List<String>>("selectedPatches")
|
||||
val options = call.argument<Map<String, Map<String, Any>>>("options")
|
||||
val tmpDirPath = call.argument<String>("tmpDirPath")
|
||||
val keyStoreFilePath = call.argument<String>("keyStoreFilePath")
|
||||
val keystorePassword = call.argument<String>("keystorePassword")
|
||||
|
||||
if (
|
||||
inFilePath != null &&
|
||||
outFilePath != null &&
|
||||
selectedPatches != null &&
|
||||
options != null &&
|
||||
tmpDirPath != null &&
|
||||
keyStoreFilePath != null &&
|
||||
keystorePassword != null
|
||||
) {
|
||||
cancel = false
|
||||
runPatcher(
|
||||
result,
|
||||
inFilePath,
|
||||
outFilePath,
|
||||
selectedPatches,
|
||||
options,
|
||||
tmpDirPath,
|
||||
keyStoreFilePath,
|
||||
keystorePassword
|
||||
)
|
||||
} else result.error(
|
||||
"INVALID_ARGUMENTS",
|
||||
"Invalid arguments",
|
||||
"One or more arguments are missing"
|
||||
)
|
||||
}
|
||||
|
||||
"stopPatcher" -> {
|
||||
cancel = true
|
||||
stopResult = result
|
||||
}
|
||||
|
||||
"getPatches" -> {
|
||||
val patchBundleFilePath = call.argument<String>("patchBundleFilePath")!!
|
||||
|
||||
try {
|
||||
val patchBundleFile = File(patchBundleFilePath)
|
||||
patchBundleFile.setWritable(false)
|
||||
patches = loadPatchesFromDex(
|
||||
setOf(patchBundleFile),
|
||||
optimizedDexDirectory = codeCacheDir
|
||||
)
|
||||
} catch (t: Throwable) {
|
||||
return@setMethodCallHandler result.error(
|
||||
"PATCH_BUNDLE_ERROR",
|
||||
"Failed to load patch bundle",
|
||||
t.stackTraceToString()
|
||||
)
|
||||
}
|
||||
|
||||
JSONArray().apply {
|
||||
patches.forEach {
|
||||
JSONObject().apply {
|
||||
put("name", it.name)
|
||||
put("description", it.description)
|
||||
put("excluded", !it.use)
|
||||
put("compatiblePackages", JSONArray().apply {
|
||||
it.compatiblePackages?.forEach { (name, versions) ->
|
||||
val compatiblePackageJson = JSONObject().apply {
|
||||
put("name", name)
|
||||
put(
|
||||
"versions",
|
||||
JSONArray().apply {
|
||||
versions?.forEach { version ->
|
||||
put(version)
|
||||
}
|
||||
})
|
||||
}
|
||||
put(compatiblePackageJson)
|
||||
}
|
||||
})
|
||||
put("options", JSONArray().apply {
|
||||
it.options.values.forEach { option ->
|
||||
JSONObject().apply {
|
||||
put("key", option.key)
|
||||
put("title", option.title)
|
||||
put("description", option.description)
|
||||
put("required", option.required)
|
||||
|
||||
fun JSONObject.putValue(
|
||||
value: Any?,
|
||||
key: String = "value"
|
||||
) = if (value is Array<*>) put(
|
||||
key,
|
||||
JSONArray().apply {
|
||||
value.forEach { put(it) }
|
||||
})
|
||||
else put(key, value)
|
||||
|
||||
putValue(option.default)
|
||||
|
||||
option.values?.let { values ->
|
||||
put(
|
||||
"values",
|
||||
JSONObject().apply {
|
||||
values.forEach { (key, value) ->
|
||||
putValue(value, key)
|
||||
}
|
||||
})
|
||||
} ?: put("values", null)
|
||||
put("type", option.type)
|
||||
}.let(::put)
|
||||
}
|
||||
})
|
||||
}.let(::put)
|
||||
}
|
||||
}.toString().let(result::success)
|
||||
}
|
||||
|
||||
"installApk" -> {
|
||||
val apkPath = call.argument<String>("apkPath")!!
|
||||
PackageInstallerManager.result = result
|
||||
installApk(apkPath)
|
||||
}
|
||||
|
||||
"uninstallApp" -> {
|
||||
val packageName = call.argument<String>("packageName")!!
|
||||
uninstallApp(packageName)
|
||||
PackageInstallerManager.result = result
|
||||
}
|
||||
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun openBrowser(query: String?) {
|
||||
val intent = Intent(Intent.ACTION_WEB_SEARCH).apply {
|
||||
putExtra(SearchManager.QUERY, query)
|
||||
}
|
||||
if (intent.resolveActivity(packageManager) != null) {
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun runPatcher(
|
||||
result: MethodChannel.Result,
|
||||
inFilePath: String,
|
||||
outFilePath: String,
|
||||
selectedPatches: List<String>,
|
||||
options: Map<String, Map<String, Any>>,
|
||||
tmpDirPath: String,
|
||||
keyStoreFilePath: String,
|
||||
keystorePassword: String
|
||||
) {
|
||||
val inFile = File(inFilePath)
|
||||
// Necessary because the file is copied from a nonwriteable location.
|
||||
inFile.setWritable(true)
|
||||
inFile.setReadable(true)
|
||||
val outFile = File(outFilePath)
|
||||
val keyStoreFile = File(keyStoreFilePath)
|
||||
val tmpDir = File(tmpDirPath)
|
||||
|
||||
Thread {
|
||||
fun updateProgress(progress: Double, header: String, log: String) {
|
||||
handler.post {
|
||||
installerChannel.invokeMethod(
|
||||
"update",
|
||||
mapOf(
|
||||
"progress" to progress,
|
||||
"header" to header,
|
||||
"log" to log
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun postStop() = handler.post { stopResult!!.success(null) }
|
||||
|
||||
fun cancel(block: () -> Unit = {}): Boolean {
|
||||
if (cancel) {
|
||||
block()
|
||||
postStop()
|
||||
}
|
||||
|
||||
return cancel
|
||||
}
|
||||
|
||||
|
||||
// Setup logger
|
||||
Logger.getLogger("").apply {
|
||||
handlers.forEach { handler ->
|
||||
handler.close()
|
||||
removeHandler(handler)
|
||||
}
|
||||
|
||||
object : java.util.logging.Handler() {
|
||||
override fun publish(record: LogRecord) {
|
||||
if (cancel) return
|
||||
if (
|
||||
record.loggerName?.startsWith("app.revanced") == true ||
|
||||
// Logger in class brut.util.OS.
|
||||
record.loggerName == ""
|
||||
) updateProgress(-1.0, "", record.message)
|
||||
}
|
||||
|
||||
override fun flush() = Unit
|
||||
override fun close() = flush()
|
||||
}.let(::addHandler)
|
||||
}
|
||||
|
||||
try {
|
||||
updateProgress(0.0, "Reading APK...", "Reading APK")
|
||||
|
||||
val patcher = Patcher(
|
||||
PatcherConfig(
|
||||
inFile,
|
||||
tmpDir,
|
||||
Aapt.binary(applicationContext).absolutePath,
|
||||
tmpDir.path,
|
||||
)
|
||||
)
|
||||
|
||||
if (cancel(patcher::close)) return@Thread
|
||||
updateProgress(0.02, "Loading patches...", "Loading patches")
|
||||
|
||||
val patches = patches.filter { patch ->
|
||||
val isCompatible = patch.compatiblePackages?.any { (name, _) ->
|
||||
name == patcher.context.packageMetadata.packageName
|
||||
} ?: false
|
||||
|
||||
val compatibleOrUniversal =
|
||||
isCompatible || patch.compatiblePackages.isNullOrEmpty()
|
||||
|
||||
compatibleOrUniversal && selectedPatches.any { it == patch.name }
|
||||
}.onEach { patch ->
|
||||
options[patch.name]?.forEach { (key, value) ->
|
||||
patch.options[key] = value
|
||||
}
|
||||
}.toSet()
|
||||
|
||||
if (cancel(patcher::close)) return@Thread
|
||||
updateProgress(0.05, "Executing...", "")
|
||||
|
||||
val patcherResult = patcher.use {
|
||||
it += patches
|
||||
|
||||
runBlocking {
|
||||
// Update the progress bar every time a patch is executed from 0.15 to 0.7
|
||||
val totalPatchesCount = patches.size
|
||||
val progressStep = 0.55 / totalPatchesCount
|
||||
var progress = 0.05
|
||||
|
||||
patcher().collect(FlowCollector { patchResult: PatchResult ->
|
||||
if (cancel(patcher::close)) return@FlowCollector
|
||||
|
||||
val msg = patchResult.exception?.let {
|
||||
val writer = StringWriter()
|
||||
it.printStackTrace(PrintWriter(writer))
|
||||
"${patchResult.patch.name} failed: $writer"
|
||||
} ?: run {
|
||||
"${patchResult.patch.name} succeeded"
|
||||
}
|
||||
|
||||
updateProgress(progress, "", msg)
|
||||
progress += progressStep
|
||||
})
|
||||
}
|
||||
|
||||
if (cancel(patcher::close)) return@Thread
|
||||
updateProgress(0.75, "Building...", "")
|
||||
|
||||
patcher.get()
|
||||
}
|
||||
|
||||
if (cancel(patcher::close)) return@Thread
|
||||
|
||||
patcherResult.applyTo(inFile)
|
||||
|
||||
if (cancel(patcher::close)) return@Thread
|
||||
|
||||
ApkUtils.signApk(
|
||||
inFile,
|
||||
outFile,
|
||||
"ReVanced",
|
||||
ApkUtils.KeyStoreDetails(
|
||||
keyStoreFile,
|
||||
keystorePassword,
|
||||
"alias",
|
||||
keystorePassword
|
||||
)
|
||||
)
|
||||
|
||||
updateProgress(.85, "Patched", "Patched APK")
|
||||
} catch (ex: Throwable) {
|
||||
if (!cancel) {
|
||||
val stack = ex.stackTraceToString()
|
||||
updateProgress(
|
||||
-100.0,
|
||||
"Failed",
|
||||
"An error occurred:\n$stack"
|
||||
)
|
||||
}
|
||||
} finally {
|
||||
inFile.delete()
|
||||
tmpDir.deleteRecursively()
|
||||
}
|
||||
|
||||
handler.post { result.success(null) }
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun installApk(apkPath: String) {
|
||||
val packageInstaller: PackageInstaller = applicationContext.packageManager.packageInstaller
|
||||
val sessionParams =
|
||||
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
|
||||
val sessionId: Int = packageInstaller.createSession(sessionParams)
|
||||
val session: PackageInstaller.Session = packageInstaller.openSession(sessionId)
|
||||
session.use { activeSession ->
|
||||
val sessionOutputStream = activeSession.openWrite(applicationContext.packageName, 0, -1)
|
||||
sessionOutputStream.use { outputStream ->
|
||||
val apkFile = File(apkPath)
|
||||
apkFile.inputStream().use { inputStream ->
|
||||
inputStream.copyTo(outputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
val receiverIntent = Intent(applicationContext, InstallerReceiver::class.java).apply {
|
||||
action = "APP_INSTALL_ACTION"
|
||||
}
|
||||
val receiverPendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
sessionId,
|
||||
receiverIntent,
|
||||
PackageInstallerManager.flags
|
||||
)
|
||||
session.commit(receiverPendingIntent.intentSender)
|
||||
session.close()
|
||||
}
|
||||
|
||||
private fun uninstallApp(packageName: String) {
|
||||
val packageInstaller: PackageInstaller = applicationContext.packageManager.packageInstaller
|
||||
val receiverIntent = Intent(applicationContext, UninstallerReceiver::class.java).apply {
|
||||
action = "APP_UNINSTALL_ACTION"
|
||||
}
|
||||
val receiverPendingIntent =
|
||||
PendingIntent.getBroadcast(context, 0, receiverIntent, PackageInstallerManager.flags)
|
||||
packageInstaller.uninstall(packageName, receiverPendingIntent.intentSender)
|
||||
}
|
||||
|
||||
object PackageInstallerManager {
|
||||
var result: MethodChannel.Result? = null
|
||||
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
} else {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package app.revanced.manager.flutter.utils
|
||||
|
||||
import android.content.Context
|
||||
import java.io.File
|
||||
|
||||
object Aapt {
|
||||
fun binary(context: Context): File {
|
||||
return File(context.applicationInfo.nativeLibraryDir).resolveAapt()
|
||||
}
|
||||
}
|
||||
|
||||
private fun File.resolveAapt() = resolve(list { _, f -> !File(f).isDirectory && f.contains("aapt") }!!.first())
|
||||
@@ -1,32 +0,0 @@
|
||||
package app.revanced.manager.flutter.utils.packageInstaller
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInstaller
|
||||
import app.revanced.manager.flutter.MainActivity
|
||||
|
||||
class InstallerReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
when (val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)) {
|
||||
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||
val confirmationIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
|
||||
if (confirmationIntent != null) {
|
||||
context.startActivity(confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
val packageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
|
||||
val message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
|
||||
val otherPackageName = intent.getStringExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME)
|
||||
MainActivity.PackageInstallerManager.result!!.success(mapOf(
|
||||
"status" to status,
|
||||
"packageName" to packageName,
|
||||
"message" to message,
|
||||
"otherPackageName" to otherPackageName
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package app.revanced.manager.flutter.utils.packageInstaller
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInstaller
|
||||
import app.revanced.manager.flutter.MainActivity
|
||||
|
||||
class UninstallerReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
when (val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)) {
|
||||
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||
val confirmationIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
|
||||
if (confirmationIntent != null) {
|
||||
context.startActivity(confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
MainActivity.PackageInstallerManager.result!!.success(status)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
@@ -1,30 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="256"
|
||||
android:viewportHeight="256">
|
||||
<group android:scaleX="0.23"
|
||||
android:scaleY="0.23"
|
||||
android:translateX="98.56"
|
||||
android:translateY="98.56">
|
||||
<path
|
||||
android:pathData="M253.85,4.9C254.32,3.82 254.22,2.57 253.58,1.58C252.93,0.6 251.83,0 250.64,0C243.29,0 230.47,0 225.95,0C224.96,0 224.06,0.59 223.66,1.5C216.03,18.88 144.1,182.7 130.29,214.16C129.89,215.07 128.99,215.66 128,215.66C127.01,215.66 126.11,215.07 125.71,214.16C111.9,182.7 39.97,18.88 32.34,1.5C31.94,0.59 31.04,0 30.05,0C25.53,0 12.71,0 5.36,0C4.17,0 3.07,0.6 2.42,1.58C1.78,2.57 1.68,3.82 2.15,4.9C16.78,38.3 101.47,231.61 111.24,253.9C111.8,255.18 113.06,256 114.45,256C120.29,256 135.71,256 141.55,256C142.94,256 144.2,255.18 144.76,253.9C154.52,231.61 239.22,38.3 253.85,4.9Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M130.59,131.75C130.06,132.68 129.07,133.25 128,133.25C126.93,133.25 125.94,132.68 125.4,131.75C113.45,111.06 63.88,25.19 51.93,4.5C51.4,3.57 51.4,2.43 51.93,1.5C52.47,0.57 53.46,-0 54.53,-0L201.47,-0C202.54,-0 203.53,0.57 204.06,1.5C204.6,2.43 204.6,3.57 204.06,4.5C192.12,25.19 142.54,111.06 130.59,131.75Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="128"
|
||||
android:startY="-0"
|
||||
android:endX="128"
|
||||
android:endY="254.6"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FFF04E98"/>
|
||||
<item android:offset="0.5" android:color="#FF5F65D4"/>
|
||||
<item android:offset="1" android:color="#FF4E98F0"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</group>
|
||||
</vector>
|
||||
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 5.3 KiB |
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:keep="@drawable/ic_notification" />
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_launcher_round</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_launcher_round</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#1B1B1B</color>
|
||||
</resources>
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<cache-path name="cache" path="." />
|
||||
</paths>
|
||||
@@ -1,7 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -1,40 +0,0 @@
|
||||
import com.android.build.api.dsl.CommonExtension
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/revanced/registry")
|
||||
credentials {
|
||||
username = providers.gradleProperty("gpr.user").orNull ?: System.getenv("GITHUB_ACTOR")
|
||||
password = providers.gradleProperty("gpr.key").orNull ?: System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layout.buildDirectory = File("../build")
|
||||
|
||||
project(":screenshot_callback") {
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
afterEvaluate {
|
||||
extensions.findByName("android")?.let {
|
||||
it as CommonExtension<*, *, *, *, *, *>
|
||||
if (it.compileSdk != null && it.compileSdk!! < 31)
|
||||
it.compileSdk = 34
|
||||
}
|
||||
}
|
||||
|
||||
layout.buildDirectory = rootProject.layout.buildDirectory.file(name).get().asFile
|
||||
evaluationDependsOn(":app")
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.daemon=true
|
||||
org.gradle.caching=true
|
||||
android.nonTransitiveRClass=false
|
||||
android.nonFinalResIds=false
|
||||
@@ -1,9 +0,0 @@
|
||||
[versions]
|
||||
revanced-patcher = "21.0.0"
|
||||
revanced-library = "3.1.0"
|
||||
desugar_jdk_libs = "2.1.4"
|
||||
|
||||
[libraries]
|
||||
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
|
||||
revanced-library = { module = "app.revanced:revanced-library", version.ref = "revanced-library" }
|
||||
desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" }
|
||||
@@ -1,8 +0,0 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=8d97a97984f6cbd2b85fe4c60a743440a347544bf18818048e611f5288d46c94
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -1,24 +0,0 @@
|
||||
pluginManagement {
|
||||
val properties = java.util.Properties().apply {
|
||||
load(file("local.properties").inputStream())
|
||||
}
|
||||
|
||||
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.9.0" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.1.10" apply false
|
||||
}
|
||||
|
||||
include(":app")
|
||||