Support upgrade loader version

This commit is contained in:
Nullptr 2022-07-05 21:54:13 +08:00
parent 0ef34fa633
commit 279e95b57b
No known key found for this signature in database
GPG Key ID: 0B9D02052FF536BD
15 changed files with 286 additions and 65 deletions

View File

@ -16,7 +16,7 @@ buildscript {
}
dependencies {
classpath("org.eclipse.jgit:org.eclipse.jgit:6.0.0.202111291000-r")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21")
classpath(kotlin("gradle-plugin", version = "1.7.0"))
}
}

View File

@ -38,7 +38,7 @@ android {
}
composeOptions {
kotlinCompilerExtensionVersion = "1.2.0-beta02"
kotlinCompilerExtensionVersion = "1.2.0"
}
sourceSets["main"].assets.srcDirs(rootProject.projectDir.resolve("out/assets"))
@ -78,22 +78,22 @@ dependencies {
annotationProcessor("androidx.room:room-compiler:$roomVersion")
compileOnly("dev.rikka.hidden:stub:2.3.1")
implementation("dev.rikka.hidden:compat:2.3.1")
implementation("androidx.core:core-ktx:1.7.0")
implementation("androidx.activity:activity-compose:1.6.0-alpha03")
implementation("androidx.compose.material:material-icons-extended:1.2.0-beta02")
implementation("androidx.compose.material3:material3:1.0.0-alpha12")
implementation("androidx.compose.runtime:runtime-livedata:1.2.0-beta02")
implementation("androidx.compose.ui:ui:1.2.0-beta02")
implementation("androidx.compose.ui:ui-tooling:1.2.0-beta02")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-rc01")
implementation("androidx.navigation:navigation-compose:2.5.0-rc01")
implementation("androidx.core:core-ktx:1.8.0")
implementation("androidx.activity:activity-compose:1.6.0-alpha05")
implementation("androidx.compose.material:material-icons-extended:1.3.0-alpha01")
implementation("androidx.compose.material3:material3:1.0.0-alpha14")
implementation("androidx.compose.runtime:runtime-livedata:1.3.0-alpha01")
implementation("androidx.compose.ui:ui:1.3.0-alpha01")
implementation("androidx.compose.ui:ui-tooling:1.3.0-alpha01")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0")
implementation("androidx.navigation:navigation-compose:2.5.0")
implementation("androidx.preference:preference:1.2.0")
implementation("androidx.room:room-ktx:$roomVersion")
implementation("androidx.room:room-runtime:$roomVersion")
implementation("com.google.accompanist:accompanist-drawablepainter:0.24.9-beta")
implementation("com.google.accompanist:accompanist-navigation-animation:0.24.9-beta")
implementation("com.google.accompanist:accompanist-swiperefresh:0.24.9-beta")
implementation("com.google.android.material:material:1.6.0")
implementation("com.google.accompanist:accompanist-drawablepainter:0.24.11-rc")
implementation("com.google.accompanist:accompanist-navigation-animation:0.24.11-rc")
implementation("com.google.accompanist:accompanist-swiperefresh:0.24.11-rc")
implementation("com.google.android.material:material:1.6.1")
implementation("com.google.code.gson:gson:2.9.0")
implementation("dev.rikka.shizuku:api:12.1.0")
implementation("dev.rikka.shizuku:provider:12.1.0")

View File

@ -1,5 +1,6 @@
-keep class com.beust.jcommander.** { *; }
-keep class org.lsposed.lspatch.Patcher$Options { *; }
-keep class org.lsposed.lspatch.share.LSPConfig { *; }
-keep class org.lsposed.lspatch.share.PatchConfig { *; }
-keepclassmembers class org.lsposed.patch.LSPatch {
private <fields>;

View File

@ -7,6 +7,7 @@ import kotlinx.coroutines.withContext
import org.lsposed.lspatch.Constants.PATCH_FILE_SUFFIX
import org.lsposed.lspatch.Constants.PREFS_STORAGE_DIRECTORY
import org.lsposed.lspatch.config.MyKeyStore
import org.lsposed.lspatch.share.PatchConfig
import org.lsposed.patch.LSPatch
import org.lsposed.patch.util.Logger
import java.io.IOException
@ -14,26 +15,21 @@ import java.io.IOException
object Patcher {
class Options(
private val apkPaths: List<String>,
private val debuggable: Boolean,
private val sigbypassLevel: Int,
private val v1: Boolean,
private val v2: Boolean,
private val useManager: Boolean,
private val overrideVersionCode: Boolean,
private val verbose: Boolean,
private val config: PatchConfig,
private val apkPaths: List<String>,
private val embeddedModules: List<String>?
) {
fun toStringArray(): Array<String> {
return buildList {
addAll(apkPaths)
add("-o"); add(lspApp.tmpApkDir.absolutePath)
if (debuggable) add("-d")
add("-l"); add(sigbypassLevel.toString())
add("--v1"); add(v1.toString())
add("--v2"); add(v2.toString())
if (useManager) add("--manager")
if (overrideVersionCode) add("-r")
if (config.debuggable) add("-d")
add("-l"); add(config.sigBypassLevel.toString())
add("--v1"); add(config.v1.toString())
add("--v2"); add(config.v2.toString())
if (config.useManager) add("--manager")
if (config.overrideVersionCode) add("-r")
if (verbose) add("-v")
embeddedModules?.forEach {
add("-m"); add(it)

View File

@ -0,0 +1,32 @@
package org.lsposed.lspatch.ui.component
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
@Preview
@Composable
fun LoadingDialog() {
Dialog(
onDismissRequest = {},
properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false)
) {
Box(
modifier = Modifier
.size(100.dp)
.background(Color.White, shape = RoundedCornerShape(8.dp)),
contentAlignment = Alignment.Center,
content = { CircularProgressIndicator() }
)
}
}

View File

@ -0,0 +1,49 @@
package org.lsposed.lspatch.ui.component.settings
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Api
import androidx.compose.material3.Switch
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.Preview
@Composable
fun SettingsSwitch(
modifier: Modifier = Modifier,
checked: Boolean,
enabled: Boolean = true,
onClick: () -> Unit,
icon: ImageVector? = null,
title: String,
desc: String? = null,
extraContent: (@Composable ColumnScope.() -> Unit)? = null
) {
SettingsSlot(modifier, enabled, onClick, icon, title, desc, extraContent) {
Switch(checked = checked, onCheckedChange = { onClick() })
}
}
@Preview
@Composable
private fun SettingsCheckBoxPreview() {
var checked1 by remember { mutableStateOf(false) }
var checked2 by remember { mutableStateOf(false) }
Column {
SettingsSwitch(
checked = checked1,
onClick = { checked1 = !checked1 },
title = "Title",
desc = "Description"
)
SettingsSwitch(
checked = checked2,
onClick = { checked2 = !checked2 },
icon = Icons.Outlined.Api,
title = "Title",
desc = "Description"
)
}
}

View File

@ -1,6 +1,9 @@
package org.lsposed.lspatch.ui.page
import android.app.Activity
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Log
@ -38,12 +41,15 @@ import org.lsposed.lspatch.R
import org.lsposed.lspatch.config.ConfigManager
import org.lsposed.lspatch.database.entity.Module
import org.lsposed.lspatch.lspApp
import org.lsposed.lspatch.share.LSPConfig
import org.lsposed.lspatch.ui.component.AppItem
import org.lsposed.lspatch.ui.component.LoadingDialog
import org.lsposed.lspatch.ui.util.LocalNavController
import org.lsposed.lspatch.ui.util.LocalSnackbarHost
import org.lsposed.lspatch.ui.util.observeState
import org.lsposed.lspatch.ui.util.setState
import org.lsposed.lspatch.ui.viewmodel.ManageViewModel
import org.lsposed.lspatch.ui.viewmodel.ManageViewModel.ViewAction
import org.lsposed.lspatch.util.LSPPackageManager
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
import org.lsposed.lspatch.util.ShizukuApi
@ -240,6 +246,27 @@ private fun Body() {
navController.currentBackStackEntry!!.setState("isCancelled", null)
}
}
if (viewModel.processingUpdate) LoadingDialog()
viewModel.updateLoaderResult?.let {
val updateSuccessfully = stringResource(R.string.manage_update_loader_successfully)
val updateFailed = stringResource(R.string.manage_update_loader_failed)
val copyError = stringResource(R.string.copy_error)
LaunchedEffect(Unit) {
it.onSuccess {
LSPPackageManager.fetchAppList()
snackbarHost.showSnackbar(updateSuccessfully)
}.onFailure {
val result = snackbarHost.showSnackbar(updateFailed, copyError)
if (result == SnackbarResult.ActionPerformed) {
val cm = lspApp.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
cm.setPrimaryClip(ClipData.newPlainText("LSPatch", it.toString()))
}
}
viewModel.dispatch(ViewAction.ClearUpdateLoaderResult)
}
}
LazyColumn {
items(
items = viewModel.appList,
@ -272,6 +299,22 @@ private fun Body() {
}
)
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
val shizukuUnavailable = stringResource(R.string.shizuku_unavailable)
if (it.second.lspConfig.VERSION_CODE >= 319 && it.second.lspConfig.VERSION_CODE < LSPConfig.instance.VERSION_CODE) {
DropdownMenuItem(
text = { Text(stringResource(R.string.manage_update_loader)) },
onClick = {
expanded = false
scope.launch {
if (!ShizukuApi.isPermissionGranted) {
snackbarHost.showSnackbar(shizukuUnavailable)
} else {
viewModel.dispatch(ViewAction.UpdateLoader(it.first, it.second))
}
}
}
)
}
if (it.second.useManager) {
DropdownMenuItem(
text = { Text(stringResource(R.string.manage_module_scope)) },
@ -291,19 +334,16 @@ private fun Body() {
}
)
}
val shizukuUnavailable = stringResource(R.string.shizuku_unavailable)
val optimizeSucceed = stringResource(R.string.manage_optimize_successfully)
val optimizeFailed = stringResource(R.string.manage_optimize_failed)
DropdownMenuItem(
text = { Text(stringResource(R.string.manage_optimize)) },
onClick = {
expanded = false
if (!ShizukuApi.isPermissionGranted) {
scope.launch {
scope.launch {
if (!ShizukuApi.isPermissionGranted) {
snackbarHost.showSnackbar(shizukuUnavailable)
}
} else {
scope.launch {
} else {
val result = ShizukuApi.performDexOptMode(it.first.app.packageName)
snackbarHost.showSnackbar(if (result) optimizeSucceed else optimizeFailed)
}

View File

@ -81,7 +81,7 @@ fun NewPatchPage(from: String, entry: NavBackStackEntry) {
}
}
LaunchedEffect(Unit) {
lspApp.tmpApkDir.listFiles()?.forEach(File::delete)
LSPPackageManager.cleanTmpApkDir()
if (isCancelled == true) navController.popBackStack()
else when (from) {
"storage" -> storageLauncher.launch(arrayOf("application/vnd.android.package-archive"))
@ -323,7 +323,7 @@ private fun DoPatchBody(modifier: Modifier) {
val shizukuUnavailable = stringResource(R.string.shizuku_unavailable)
val installSuccessfully = stringResource(R.string.patch_install_successfully)
val installFailed = stringResource(R.string.patch_install_failed)
val copyError = stringResource(R.string.patch_copy_error)
val copyError = stringResource(R.string.copy_error)
var installing by remember { mutableStateOf(false) }
if (installing) InstallDialog(viewModel.patchApp) { status, message ->
scope.launch {
@ -377,7 +377,7 @@ private fun DoPatchBody(modifier: Modifier) {
val cm = lspApp.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
cm.setPrimaryClip(ClipData.newPlainText("LSPatch", viewModel.logs.joinToString { it.second + "\n" }))
},
content = { Text(stringResource(R.string.patch_copy_error)) }
content = { Text(stringResource(R.string.copy_error)) }
)
}
}

View File

@ -1,27 +1,113 @@
package org.lsposed.lspatch.ui.viewmodel
import android.content.pm.PackageInstaller
import android.util.Base64
import android.util.Log
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.lsposed.lspatch.Patcher
import org.lsposed.lspatch.lspApp
import org.lsposed.lspatch.share.Constants
import org.lsposed.lspatch.share.PatchConfig
import org.lsposed.lspatch.util.LSPPackageManager
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
import org.lsposed.patch.util.Logger
import java.io.FileNotFoundException
import java.util.zip.ZipFile
private const val TAG = "ManageViewModel"
class ManageViewModel : ViewModel() {
sealed class ViewAction {
data class UpdateLoader(val appInfo: AppInfo, val config: PatchConfig) : ViewAction()
object ClearUpdateLoaderResult : ViewAction()
}
val appList: List<Pair<AppInfo, PatchConfig>> by derivedStateOf {
LSPPackageManager.appList.mapNotNull { appInfo ->
appInfo.app.metaData?.getString("lspatch")?.let {
val json = Base64.decode(it, Base64.DEFAULT).toString(Charsets.UTF_8)
Log.d(TAG, "Read patched config: $json")
appInfo to Gson().fromJson(json, PatchConfig::class.java)
}
}.also {
Log.d(TAG, "Loaded ${it.size} patched apps")
}
}
var processingUpdate by mutableStateOf(false)
private set
var updateLoaderResult: Result<Unit>? by mutableStateOf(null)
private set
private val logger = object : Logger() {
override fun d(msg: String) {
if (verbose) Log.d(TAG, msg)
}
override fun i(msg: String) {
Log.i(TAG, msg)
}
override fun e(msg: String) {
Log.e(TAG, msg)
}
}
fun dispatch(action: ViewAction) {
when (action) {
is ViewAction.UpdateLoader -> updateLoader(action.appInfo, action.config)
is ViewAction.ClearUpdateLoaderResult -> updateLoaderResult = null
}
}
private fun updateLoader(appInfo: AppInfo, config: PatchConfig) {
Log.i(TAG, "Update loader for ${appInfo.app.packageName}")
viewModelScope.launch {
processingUpdate = true
val result = runCatching {
withContext(Dispatchers.IO) {
LSPPackageManager.cleanTmpApkDir()
val apkPaths = listOf(appInfo.app.sourceDir) + (appInfo.app.splitSourceDirs ?: emptyArray())
val patchPaths = mutableListOf<String>()
val embeddedModulePaths = if (config.useManager) emptyList<String>() else null
for (apk in apkPaths) {
ZipFile(apk).use { zip ->
var entry = zip.getEntry(Constants.ORIGINAL_APK_ASSET_PATH)
if (entry == null) entry = zip.getEntry("assets/lspatch/origin_apk.bin")
if (entry == null) throw FileNotFoundException("Original apk entry not found for $apk")
zip.getInputStream(entry).use { input ->
val dst = lspApp.tmpApkDir.resolve(apk.substringAfterLast('/'))
patchPaths.add(dst.absolutePath)
dst.outputStream().use { output ->
input.copyTo(output)
}
}
}
}
Patcher.patch(
logger, Patcher.Options(
verbose = true,
config = config,
apkPaths = patchPaths,
embeddedModules = embeddedModulePaths
)
)
val (status, message) = LSPPackageManager.install()
if (status != PackageInstaller.STATUS_SUCCESS) throw RuntimeException(message)
}
}
processingUpdate = false
updateLoaderResult = result
}
}
}

View File

@ -10,10 +10,10 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import org.lsposed.lspatch.Patcher
import org.lsposed.lspatch.lspApp
import org.lsposed.lspatch.share.PatchConfig
import org.lsposed.lspatch.util.LSPPackageManager
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
import org.lsposed.patch.util.Logger
import java.io.File
private const val TAG = "NewPatchViewModel"
@ -82,13 +82,9 @@ class NewPatchViewModel : ViewModel() {
Log.d(TAG, "Submit patch")
if (useManager) embeddedModules.clear()
patchOptions = Patcher.Options(
apkPaths = listOf(patchApp.app.sourceDir) + (patchApp.app.splitSourceDirs ?: emptyArray()),
debuggable = debuggable,
sigbypassLevel = sigBypassLevel,
v1 = sign[0], v2 = sign[1],
useManager = useManager,
overrideVersionCode = overrideVersionCode,
verbose = true,
config = PatchConfig(useManager, debuggable, overrideVersionCode, sign[0], sign[1], sigBypassLevel, null, null),
apkPaths = listOf(patchApp.app.sourceDir) + (patchApp.app.splitSourceDirs ?: emptyArray()),
embeddedModules = embeddedModules.flatMap { listOf(it.app.sourceDir) + (it.app.splitSourceDirs ?: emptyArray()) }
)
patchState = PatchState.PATCHING
@ -105,7 +101,7 @@ class NewPatchViewModel : ViewModel() {
logger.e(t.stackTraceToString())
PatchState.ERROR
} finally {
lspApp.tmpApkDir.listFiles()?.forEach(File::delete)
LSPPackageManager.cleanTmpApkDir()
}
}
}

View File

@ -68,7 +68,14 @@ object LSPPackageManager {
fun getIcon(appInfo: AppInfo) = appIcon[appInfo.app.packageName]!!
suspend fun cleanTmpApkDir() {
withContext(Dispatchers.IO) {
lspApp.tmpApkDir.listFiles()?.forEach(File::delete)
}
}
suspend fun install(): Pair<Int, String?> {
Log.i(TAG, "Perform install patched apks")
var status = PackageInstaller.STATUS_FAILURE
var message: String? = null
withContext(Dispatchers.IO) {
@ -85,6 +92,7 @@ object LSPPackageManager {
?: throw IOException("DocumentFile is null")
root.listFiles().forEach { file ->
if (file.name?.endsWith(PATCH_FILE_SUFFIX) != true) return@forEach
Log.d(TAG, "Add ${file.name}")
val input = lspApp.contentResolver.openInputStream(file.uri)
?: throw IOException("Cannot open input stream")
input.use {
@ -180,7 +188,7 @@ object LSPPackageManager {
}
AppInfo(app, app.packageName)
}.recoverCatching { t ->
lspApp.tmpApkDir.listFiles()?.forEach(File::delete)
cleanTmpApkDir()
Log.e(TAG, "Failed to load apks", t)
throw t
}

View File

@ -4,6 +4,7 @@
<string name="installing">Installing</string>
<string name="uninstall">Uninstall</string>
<string name="uninstalling">Uninstalling</string>
<string name="copy_error">Copy error</string>
<string name="shizuku_available">Shizuku service available</string>
<string name="shizuku_unavailable">Shizuku service not connected</string>
<string name="page_repo">Repo</string>
@ -26,6 +27,9 @@
<string name="page_manage">Manage</string>
<string name="manage_loading">Loading</string>
<string name="manage_no_apps">No patched apps yet</string>
<string name="manage_update_loader">Update loader</string>
<string name="manage_update_loader_successfully">Update successfully</string>
<string name="manage_update_loader_failed">Update failed</string>
<string name="manage_module_scope">Module scope</string>
<string name="manage_optimize">Optimize</string>
<string name="manage_optimize_successfully">Optimize successfully</string>
@ -58,7 +62,6 @@
<string name="patch_uninstall_text">Due to different signatures, you need to uninstall the original app before installing the patched one.\nMake sure you have backed up personal data.</string>
<string name="patch_install_successfully">Install successfully</string>
<string name="patch_install_failed">Install failed</string>
<string name="patch_copy_error">Copy error</string>
<!-- Select Apps Page -->
<string name="page_select_apps">Select Apps</string>

View File

@ -216,16 +216,11 @@ public class LSPatch {
throw new PatchError("Failed to register signer", e);
}
String originalSignature = null;
if (sigbypassLevel > 0) {
// save the apk original signature info, to support crack signature.
originalSignature = ApkSignatureHelper.getApkSignInfo(srcApkFile.getAbsolutePath());
if (originalSignature == null || originalSignature.isEmpty()) {
throw new PatchError("get original signature failed");
}
logger.d("Original signature\n" + originalSignature);
final String originalSignature = ApkSignatureHelper.getApkSignInfo(srcApkFile.getAbsolutePath());
if (originalSignature == null || originalSignature.isEmpty()) {
throw new PatchError("get original signature failed");
}
logger.d("Original signature\n" + originalSignature);
// copy out manifest file from zlib
var manifestEntry = srcZFile.get(ANDROID_MANIFEST_XML);
@ -233,7 +228,7 @@ public class LSPatch {
throw new PatchError("Provided file is not a valid apk");
// parse the app appComponentFactory full name from the manifest file
String appComponentFactory;
final String appComponentFactory;
try (var is = manifestEntry.open()) {
var pair = ManifestParser.parseManifestFile(is);
if (pair == null)
@ -244,9 +239,9 @@ public class LSPatch {
logger.i("Patching apk...");
// modify manifest
var config = new PatchConfig(useManager, sigbypassLevel, null, appComponentFactory);
var configBytes = new Gson().toJson(config).getBytes(StandardCharsets.UTF_8);
var metadata = Base64.getEncoder().encodeToString(configBytes);
final var config = new PatchConfig(useManager, debuggableFlag, overrideVersionCode, v1, v2, sigbypassLevel, originalSignature, appComponentFactory);
final var configBytes = new Gson().toJson(config).getBytes(StandardCharsets.UTF_8);
final var metadata = Base64.getEncoder().encodeToString(configBytes);
try (var is = new ByteArrayInputStream(modifyManifestFile(manifestEntry.open(), metadata))) {
dstZFile.add(ANDROID_MANIFEST_XML, is);
} catch (Throwable e) {
@ -283,8 +278,6 @@ public class LSPatch {
}
// save lspatch config to asset..
config = new PatchConfig(useManager, sigbypassLevel, originalSignature, appComponentFactory);
configBytes = new Gson().toJson(config).getBytes(StandardCharsets.UTF_8);
try (var is = new ByteArrayInputStream(configBytes)) {
dstZFile.add(CONFIG_ASSET_PATH, is);
} catch (Throwable e) {

View File

@ -10,7 +10,7 @@ pluginManagement {
plugins {
id("com.android.library") version agpVersion
id("com.android.application") version agpVersion
id("com.google.devtools.ksp") version "1.6.21-1.0.5"
id("com.google.devtools.ksp") version "1.7.0-1.0.6"
id("dev.rikka.tools.refine") version "3.1.1"
}
}

View File

@ -3,13 +3,30 @@ package org.lsposed.lspatch.share;
public class PatchConfig {
public final boolean useManager;
public final boolean debuggable;
public final boolean overrideVersionCode;
public final boolean v1;
public final boolean v2;
public final int sigBypassLevel;
public final String originalSignature;
public final String appComponentFactory;
public final LSPConfig lspConfig;
public PatchConfig(boolean useManager, int sigBypassLevel, String originalSignature, String appComponentFactory) {
public PatchConfig(
boolean useManager,
boolean debuggable,
boolean overrideVersionCode,
boolean v1,
boolean v2,
int sigBypassLevel,
String originalSignature,
String appComponentFactory
) {
this.useManager = useManager;
this.debuggable = debuggable;
this.overrideVersionCode = overrideVersionCode;
this.v1 = v1;
this.v2 = v2;
this.sigBypassLevel = sigBypassLevel;
this.originalSignature = originalSignature;
this.appComponentFactory = appComponentFactory;