CI (#57)
* CI * Add patch info to metadata * Allow select local apks to patch * Fix wrong indent
This commit is contained in:
parent
9e5f805d62
commit
6924c6a3b5
|
|
@ -1,6 +1,7 @@
|
|||
name: Android CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
|
|
@ -9,6 +10,7 @@ jobs:
|
|||
build:
|
||||
name: Build on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
if: ${{ !startsWith(github.event.head_commit.message, '[skip ci]') }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
|
@ -21,32 +23,115 @@ jobs:
|
|||
submodules: 'recursive'
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Write key
|
||||
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master'
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.KEY_STORE }}" ]; then
|
||||
echo androidStorePassword='${{ secrets.KEY_STORE_PASSWORD }}' >> gradle.properties
|
||||
echo androidKeyAlias='${{ secrets.ALIAS }}' >> gradle.properties
|
||||
echo androidKeyPassword='${{ secrets.KEY_PASSWORD }}' >> gradle.properties
|
||||
echo androidStoreFile='key.jks' >> gradle.properties
|
||||
echo ${{ secrets.KEY_STORE }} | base64 --decode > key.jks
|
||||
fi
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
|
||||
- name: Build Debug
|
||||
- name: Cache gradle dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
!~/.gradle/caches/build-cache-*
|
||||
key: gradle-deps-core-${{ hashFiles('**/build.gradle.kts') }}
|
||||
restore-keys: |
|
||||
gradle-deps
|
||||
|
||||
- name: Cache gradle build
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches/build-cache-*
|
||||
~/.gradle/buildOutputCleanup/cache.properties
|
||||
key: gradle-builds-core-${{ github.sha }}
|
||||
restore-keys: |
|
||||
gradle-builds
|
||||
|
||||
- name: Cache native build
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.ccache
|
||||
patch-loader/build/.lto-cache
|
||||
key: native-cache-${{ github.sha }}
|
||||
restore-keys: native-cache-
|
||||
|
||||
- name: Install dep
|
||||
run: |
|
||||
sudo apt-get install -y ccache ninja-build
|
||||
ccache -o max_size=1G
|
||||
ccache -o hash_dir=false
|
||||
ccache -o compiler_check='%compiler% -dumpmachine; %compiler% -dumpversion'
|
||||
ccache -zp
|
||||
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
[ $(du -s ~/.gradle/wrapper | awk '{ print $1 }') -gt 250000 ] && rm -rf ~/.gradle/wrapper/* || true
|
||||
find ~/.gradle/caches -exec touch -d "2 days ago" {} + || true
|
||||
echo 'org.gradle.caching=true' >> gradle.properties
|
||||
echo 'org.gradle.parallel=true' >> gradle.properties
|
||||
echo 'org.gradle.vfs.watch=true' >> gradle.properties
|
||||
echo 'org.gradle.jvmargs=-Xmx2048m' >> gradle.properties
|
||||
./gradlew buildDebug
|
||||
echo 'android.native.buildOutput=verbose' >> gradle.properties
|
||||
ln -s $(which ninja) $(dirname $(which cmake)) # https://issuetracker.google.com/issues/206099937
|
||||
echo "cmake.dir=$(dirname $(dirname $(which cmake)))" >> local.properties
|
||||
./gradlew buildAll
|
||||
ccache -s
|
||||
|
||||
- name: Upload Debug artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: lspatch-debug
|
||||
path: |
|
||||
out/lspatch.jar
|
||||
out/manager.apk
|
||||
path: out/debug/*
|
||||
|
||||
- name: Build Release
|
||||
run: |
|
||||
echo 'org.gradle.jvmargs=-Xmx2048m' >> gradle.properties
|
||||
./gradlew buildRelease
|
||||
- name: Upload Release artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: lspatch-release
|
||||
path: out/release/*
|
||||
|
||||
- name: Upload mappings
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: mappings
|
||||
path: |
|
||||
out/lspatch.jar
|
||||
out/manager.apk
|
||||
patch-loader/build/outputs/mapping
|
||||
manager/build/outputs/mapping
|
||||
|
||||
- name: Upload symbols
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: symbols
|
||||
path: |
|
||||
patch-loader/build/symbols
|
||||
|
||||
- name: Post to channel
|
||||
if: ${{ github.event_name != 'pull_request' && success() && github.ref == 'refs/heads/master' }}
|
||||
env:
|
||||
CHANNEL_ID: ${{ secrets.CHANNEL_ID }}
|
||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
||||
COMMIT_URL: ${{ github.event.head_commit.url }}
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
export jarRelease=$(find out/release -name "*.jar")
|
||||
export managerRelease=$(find out/release -name "*.apk")
|
||||
export jarDebug=$(find out/debug -name "*.jar")
|
||||
export managerDebug=$(find out/debug -name "*.apk")
|
||||
ESCAPED=`python3 -c 'import json,os,urllib.parse; msg = json.dumps(os.environ["COMMIT_MESSAGE"]); print(urllib.parse.quote(msg if len(msg) <= 1024 else json.dumps(os.environ["COMMIT_URL"])))'`
|
||||
curl -v "https://api.telegram.org/bot${BOT_TOKEN}/sendMediaGroup?chat_id=${CHANNEL_ID}&media=%5B%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2FjarRelease%22%7D%2C%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2FmanagerRelease%22%7D%2C%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2FjarDebug%22%7D%2C%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2FmanagerDebug%22%2C%22caption%22:${ESCAPED}%7D%5D" -F jarRelease="@$jarRelease" -F managerRelease="@$managerRelease" -F jarDebug="@$jarDebug" -F managerDebug="@$managerDebug"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ You can contribute translation [here](https://lsposed.crowdin.com/lspatch).
|
|||
|
||||
## Credits
|
||||
|
||||
- [LSPosed](https://github.com/LSPosed/LSPosed): core framework
|
||||
- [Xpatch](https://github.com/WindySha/Xpatch): fork source
|
||||
- [LSPosed](https://github.com/LSPosed/LSPosed): Core framework
|
||||
- [Xpatch](https://github.com/WindySha/Xpatch): Fork source
|
||||
- [Apkzlib](https://android.googlesource.com/platform/tools/apkzlib): Repacking tool
|
||||
|
||||
## License
|
||||
|
|
|
|||
|
|
@ -63,6 +63,10 @@ listOf("Debug", "Release").forEach { variant ->
|
|||
}
|
||||
}
|
||||
|
||||
tasks.register("buildAll") {
|
||||
dependsOn("buildDebug", "buildRelease")
|
||||
}
|
||||
|
||||
fun Project.configureBaseExtension() {
|
||||
extensions.findByType(BaseExtension::class)?.run {
|
||||
compileSdkVersion(androidCompileSdkVersion)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
val defaultManagerPackageName: String by rootProject.extra
|
||||
val apiCode: Int by rootProject.extra
|
||||
val verCode: Int by rootProject.extra
|
||||
val verName: String by rootProject.extra
|
||||
val coreVerCode: Int by rootProject.extra
|
||||
val coreVerName: String by rootProject.extra
|
||||
|
||||
|
|
@ -13,10 +15,6 @@ plugins {
|
|||
android {
|
||||
defaultConfig {
|
||||
applicationId = defaultManagerPackageName
|
||||
|
||||
buildConfigField("int", "API_CODE", """$apiCode""")
|
||||
buildConfigField("int", "CORE_VERSION_CODE", """$coreVerCode""")
|
||||
buildConfigField("String", "CORE_VERSION_NAME", """"$coreVerName"""")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
|
@ -58,8 +56,8 @@ afterEvaluate {
|
|||
task<Copy>("build$variantCapped") {
|
||||
dependsOn(tasks["assemble$variantCapped"])
|
||||
from(variant.outputs.map { it.outputFile })
|
||||
into("${rootProject.projectDir}/out")
|
||||
rename(".*.apk", "manager.apk")
|
||||
into("${rootProject.projectDir}/out/$variantLowered")
|
||||
rename(".*.apk", "manager-v$verName-$verCode-$variantLowered.apk")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -69,6 +67,7 @@ dependencies {
|
|||
implementation(projects.patch)
|
||||
implementation(projects.services.daemonService)
|
||||
implementation(projects.share.android)
|
||||
implementation(projects.share.java)
|
||||
|
||||
compileOnly("dev.rikka.hidden:stub:2.3.1")
|
||||
implementation("dev.rikka.hidden:compat:2.3.1")
|
||||
|
|
@ -86,6 +85,7 @@ dependencies {
|
|||
implementation("com.google.accompanist:accompanist-navigation-animation:0.24.5-alpha")
|
||||
implementation("com.google.accompanist:accompanist-swiperefresh:0.24.5-alpha")
|
||||
implementation("com.google.android.material:material:1.5.0")
|
||||
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")
|
||||
implementation("org.lsposed.hiddenapibypass:hiddenapibypass:4.3")
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 26 KiB |
|
|
@ -2,6 +2,8 @@ package org.lsposed.lspatch
|
|||
|
||||
object Constants {
|
||||
|
||||
const val PATCH_FILE_SUFFIX = "-lspatched.apk"
|
||||
|
||||
const val PREFS_KEYSTORE_PASSWORD = "keystore_password"
|
||||
const val PREFS_KEYSTORE_ALIAS = "keystore_alias"
|
||||
const val PREFS_KEYSTORE_ALIAS_PASSWORD = "keystore_alias_password"
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||
import org.lsposed.lspatch.util.ShizukuApi
|
||||
import java.io.File
|
||||
|
||||
const val TAG = "LSPatch Manager"
|
||||
|
||||
|
|
@ -15,6 +16,7 @@ lateinit var lspApp: LSPApplication
|
|||
class LSPApplication : Application() {
|
||||
|
||||
lateinit var prefs: SharedPreferences
|
||||
lateinit var tmpApkDir: File
|
||||
|
||||
val globalScope = CoroutineScope(Dispatchers.Default)
|
||||
|
||||
|
|
@ -22,7 +24,9 @@ class LSPApplication : Application() {
|
|||
super.onCreate()
|
||||
HiddenApiBypass.addHiddenApiExemptions("");
|
||||
lspApp = this
|
||||
lspApp.filesDir.mkdir()
|
||||
filesDir.mkdir()
|
||||
tmpApkDir = cacheDir.resolve("apk")
|
||||
tmpApkDir.mkdirs()
|
||||
prefs = lspApp.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
ShizukuApi.init()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
package org.lsposed.lspatch
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.net.toUri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
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.patch.LSPatch
|
||||
import org.lsposed.patch.util.Logger
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.nio.file.Files
|
||||
|
||||
object Patcher {
|
||||
|
||||
|
|
@ -26,12 +24,10 @@ object Patcher {
|
|||
private val verbose: Boolean,
|
||||
private val embeddedModules: List<String>?
|
||||
) {
|
||||
lateinit var outputDir: File
|
||||
|
||||
fun toStringArray(): Array<String> {
|
||||
return buildList {
|
||||
addAll(apkPaths)
|
||||
add("-o"); add(outputDir.absolutePath)
|
||||
add("-o"); add(lspApp.tmpApkDir.absolutePath)
|
||||
if (debuggable) add("-d")
|
||||
add("-l"); add(sigbypassLevel.toString())
|
||||
add("--v1"); add(v1.toString())
|
||||
|
|
@ -49,26 +45,23 @@ object Patcher {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun patch(context: Context, logger: Logger, options: Options) {
|
||||
suspend fun patch(logger: Logger, options: Options) {
|
||||
withContext(Dispatchers.IO) {
|
||||
options.outputDir = Files.createTempDirectory("patch").toFile()
|
||||
options.outputDir.listFiles()?.forEach(File::delete)
|
||||
LSPatch(logger, *options.toStringArray()).doCommandLine()
|
||||
|
||||
val uri = lspApp.prefs.getString(PREFS_STORAGE_DIRECTORY, null)?.toUri()
|
||||
?: throw IOException("Uri is null")
|
||||
val root = DocumentFile.fromTreeUri(context, uri)
|
||||
val root = DocumentFile.fromTreeUri(lspApp, uri)
|
||||
?: throw IOException("DocumentFile is null")
|
||||
root.listFiles().forEach {
|
||||
if (it.name?.endsWith("-lspatched.apk") == true) it.delete()
|
||||
if (it.name?.endsWith(PATCH_FILE_SUFFIX) == true) it.delete()
|
||||
}
|
||||
options.outputDir
|
||||
.walk()
|
||||
lspApp.tmpApkDir.walk()
|
||||
.filter { it.isFile }
|
||||
.forEach { apk ->
|
||||
val file = root.createFile("application/vnd.android.package-archive", apk.name)
|
||||
?: throw IOException("Failed to create output file")
|
||||
val output = context.contentResolver.openOutputStream(file.uri)
|
||||
val output = lspApp.contentResolver.openOutputStream(file.uri)
|
||||
?: throw IOException("Failed to open output stream")
|
||||
output.use {
|
||||
apk.inputStream().use { input ->
|
||||
|
|
|
|||
|
|
@ -66,7 +66,9 @@ private fun MainNavHost(navController: NavHostController, modifier: Modifier) {
|
|||
) {
|
||||
PageList.values().forEach { page ->
|
||||
val sb = StringBuilder(page.name)
|
||||
page.arguments.forEach { sb.append("/{${it.name}}") }
|
||||
if (page.arguments.isNotEmpty()) {
|
||||
sb.append(page.arguments.joinToString(",", "?") { "${it.name}={${it.name}}" })
|
||||
}
|
||||
composable(route = sb.toString(), arguments = page.arguments, content = page.body)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||
|
|
@ -23,10 +24,10 @@ fun AppItem(
|
|||
icon: Drawable,
|
||||
label: String,
|
||||
packageName: String,
|
||||
additionalInfo: (@Composable () -> Unit)? = null,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
checked: Boolean? = null
|
||||
checked: Boolean? = null,
|
||||
additionalContent: (@Composable () -> Unit)? = null,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
|
|
@ -47,10 +48,17 @@ fun AppItem(
|
|||
modifier = Modifier.size(32.dp),
|
||||
tint = Color.Unspecified
|
||||
)
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(text = label, style = MaterialTheme.typography.bodyMedium)
|
||||
Text(text = packageName, style = MaterialTheme.typography.bodySmall)
|
||||
additionalInfo?.invoke()
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.spacedBy(1.dp)
|
||||
) {
|
||||
Text(label)
|
||||
Text(
|
||||
text = packageName,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
additionalContent?.invoke()
|
||||
}
|
||||
if (checked != null) {
|
||||
Checkbox(
|
||||
|
|
|
|||
|
|
@ -41,8 +41,13 @@ fun SearchAppBar(
|
|||
val focusRequester = remember { FocusRequester() }
|
||||
var onSearch by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (onSearch) focusRequester.requestFocus()
|
||||
if (onSearch) {
|
||||
LaunchedEffect(Unit) { focusRequester.requestFocus() }
|
||||
}
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
keyboardController?.hide()
|
||||
}
|
||||
}
|
||||
|
||||
SmallTopAppBar(
|
||||
|
|
@ -75,8 +80,9 @@ fun SearchAppBar(
|
|||
trailingIcon = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
onClearClick()
|
||||
onSearch = false
|
||||
keyboardController?.hide()
|
||||
onClearClick()
|
||||
},
|
||||
content = { Icon(Icons.Filled.Close, null) }
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ import androidx.compose.material.icons.Icons
|
|||
import androidx.compose.material.icons.outlined.CheckCircle
|
||||
import androidx.compose.material.icons.outlined.Warning
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
|
@ -23,8 +26,8 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.launch
|
||||
import org.lsposed.lspatch.BuildConfig
|
||||
import org.lsposed.lspatch.R
|
||||
import org.lsposed.lspatch.share.LSPConfig
|
||||
import org.lsposed.lspatch.ui.util.HtmlText
|
||||
import org.lsposed.lspatch.ui.util.LocalSnackbarHost
|
||||
import org.lsposed.lspatch.util.ShizukuApi
|
||||
|
|
@ -38,12 +41,11 @@ fun HomePage() {
|
|||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.padding(horizontal = 16.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
ShizukuCard()
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCard()
|
||||
Spacer(Modifier.height(16.dp))
|
||||
SupportCard()
|
||||
}
|
||||
}
|
||||
|
|
@ -164,13 +166,13 @@ private fun InfoCard() {
|
|||
Text(text = texts.second, style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
|
||||
infoCardContent(stringResource(R.string.home_api_version) to "${BuildConfig.API_CODE}")
|
||||
infoCardContent(stringResource(R.string.home_api_version) to "${LSPConfig.instance.API_CODE}")
|
||||
|
||||
Spacer(Modifier.height(24.dp))
|
||||
infoCardContent(stringResource(R.string.home_lspatch_version) to BuildConfig.VERSION_NAME + " (${BuildConfig.VERSION_CODE})")
|
||||
infoCardContent(stringResource(R.string.home_lspatch_version) to LSPConfig.instance.VERSION_NAME + " (${LSPConfig.instance.VERSION_CODE})")
|
||||
|
||||
Spacer(Modifier.height(24.dp))
|
||||
infoCardContent(stringResource(R.string.home_framework_version) to BuildConfig.CORE_VERSION_NAME + " (${BuildConfig.CORE_VERSION_CODE})")
|
||||
infoCardContent(stringResource(R.string.home_framework_version) to LSPConfig.instance.CORE_VERSION_NAME + " (${LSPConfig.instance.CORE_VERSION_CODE})")
|
||||
|
||||
Spacer(Modifier.height(24.dp))
|
||||
infoCardContent(stringResource(R.string.home_system_version) to apiVersion)
|
||||
|
|
|
|||
|
|
@ -5,41 +5,56 @@ import android.content.Intent
|
|||
import android.util.Log
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.lsposed.lspatch.*
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.lsposed.lspatch.Constants.PREFS_STORAGE_DIRECTORY
|
||||
import org.lsposed.lspatch.R
|
||||
import org.lsposed.lspatch.TAG
|
||||
import org.lsposed.lspatch.lspApp
|
||||
import org.lsposed.lspatch.ui.component.AppItem
|
||||
import org.lsposed.lspatch.ui.util.LocalNavController
|
||||
import org.lsposed.lspatch.ui.util.LocalSnackbarHost
|
||||
import org.lsposed.lspatch.ui.viewmodel.ManageViewModel
|
||||
import org.lsposed.lspatch.util.LSPPackageManager
|
||||
import java.io.IOException
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ManagePage() {
|
||||
val viewModel = viewModel<ManageViewModel>()
|
||||
|
||||
Scaffold(
|
||||
topBar = { TopBar() },
|
||||
floatingActionButton = { Fab() }
|
||||
) { innerPadding ->
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize(),
|
||||
text = "This page is not yet implemented",
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Box(Modifier.padding(innerPadding)) {
|
||||
Body()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,6 +72,7 @@ private fun Fab() {
|
|||
val navController = LocalNavController.current
|
||||
val scope = rememberCoroutineScope()
|
||||
var shouldSelectDirectory by remember { mutableStateOf(false) }
|
||||
var showNewPatchDialog by remember { mutableStateOf(false) }
|
||||
|
||||
val errorText = stringResource(R.string.patch_select_dir_error)
|
||||
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
|
|
@ -67,7 +83,7 @@ private fun Fab() {
|
|||
context.contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||
lspApp.prefs.edit().putString(PREFS_STORAGE_DIRECTORY, uri.toString()).apply()
|
||||
Log.i(TAG, "Storage directory: ${uri.path}")
|
||||
navController.navigate(PageList.NewPatch.name)
|
||||
showNewPatchDialog = true
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error when requesting saving directory", e)
|
||||
scope.launch { snackbarHost.showSnackbar(errorText) }
|
||||
|
|
@ -103,6 +119,58 @@ private fun Fab() {
|
|||
)
|
||||
}
|
||||
|
||||
if (showNewPatchDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showNewPatchDialog = false },
|
||||
confirmButton = {},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
content = { Text(stringResource(android.R.string.cancel)) },
|
||||
onClick = { showNewPatchDialog = false }
|
||||
)
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(R.string.page_new_patch),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
TextButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.secondary),
|
||||
onClick = {
|
||||
navController.navigate(PageList.NewPatch.name + "?from=storage")
|
||||
showNewPatchDialog = false
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
text = stringResource(R.string.patch_from_storage),
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
}
|
||||
TextButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.secondary),
|
||||
onClick = {
|
||||
navController.navigate(PageList.NewPatch.name + "?from=applist")
|
||||
showNewPatchDialog = false
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
text = stringResource(R.string.patch_from_applist),
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
FloatingActionButton(
|
||||
content = { Icon(Icons.Filled.Add, stringResource(R.string.add)) },
|
||||
onClick = {
|
||||
|
|
@ -115,7 +183,7 @@ private fun Fab() {
|
|||
context.contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||
if (DocumentFile.fromTreeUri(context, uri)?.exists() == false) throw IOException("Storage directory was deleted")
|
||||
}.onSuccess {
|
||||
navController.navigate(PageList.NewPatch.name)
|
||||
showNewPatchDialog = true
|
||||
}.onFailure {
|
||||
Log.w(TAG, "Failed to take persistable permission for saved uri", it)
|
||||
lspApp.prefs.edit().putString(PREFS_STORAGE_DIRECTORY, null).apply()
|
||||
|
|
@ -125,3 +193,61 @@ private fun Fab() {
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun Body() {
|
||||
val viewModel = viewModel<ManageViewModel>()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (LSPPackageManager.appList.isEmpty()) {
|
||||
withContext(Dispatchers.IO) {
|
||||
LSPPackageManager.fetchAppList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (viewModel.appList.isEmpty()) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
text = run {
|
||||
if (LSPPackageManager.appList.isEmpty()) stringResource(R.string.manage_loading)
|
||||
else stringResource(R.string.manage_no_apps)
|
||||
},
|
||||
fontFamily = FontFamily.Serif,
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
}
|
||||
} else {
|
||||
LazyColumn {
|
||||
items(
|
||||
items = viewModel.appList,
|
||||
key = { it.first.app.packageName }
|
||||
) {
|
||||
AppItem(
|
||||
modifier = Modifier.animateItemPlacement(spring(stiffness = Spring.StiffnessLow)),
|
||||
icon = LSPPackageManager.getIcon(it.first),
|
||||
label = it.first.label,
|
||||
packageName = it.first.app.packageName,
|
||||
onClick = {}
|
||||
) {
|
||||
val text = buildAnnotatedString {
|
||||
val (text, color) =
|
||||
if (it.second.useManager) stringResource(R.string.patch_local) to MaterialTheme.colorScheme.secondary
|
||||
else stringResource(R.string.patch_portable) to MaterialTheme.colorScheme.tertiary
|
||||
append(AnnotatedString(text, SpanStyle(color = color)))
|
||||
append(" ")
|
||||
append(it.second.lspConfig.VERSION_CODE.toString())
|
||||
}
|
||||
Text(
|
||||
text = text,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontFamily = FontFamily.Serif,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import android.content.Context
|
|||
import android.content.pm.PackageInstaller
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
|
|
@ -35,6 +37,7 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.lsposed.lspatch.Patcher
|
||||
import org.lsposed.lspatch.R
|
||||
import org.lsposed.lspatch.lspApp
|
||||
|
|
@ -43,23 +46,26 @@ import org.lsposed.lspatch.ui.component.ShimmerAnimation
|
|||
import org.lsposed.lspatch.ui.component.settings.SettingsCheckBox
|
||||
import org.lsposed.lspatch.ui.component.settings.SettingsItem
|
||||
import org.lsposed.lspatch.ui.util.*
|
||||
import org.lsposed.lspatch.ui.viewmodel.AppInfo
|
||||
import org.lsposed.lspatch.ui.viewmodel.NewPatchViewModel
|
||||
import org.lsposed.lspatch.ui.viewmodel.NewPatchViewModel.PatchState
|
||||
import org.lsposed.lspatch.util.LSPPackageInstaller
|
||||
import org.lsposed.lspatch.util.LSPPackageManager
|
||||
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
|
||||
import org.lsposed.lspatch.util.ShizukuApi
|
||||
import org.lsposed.patch.util.Logger
|
||||
import java.io.File
|
||||
|
||||
private const val TAG = "NewPatchPage"
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun NewPatchPage(entry: NavBackStackEntry) {
|
||||
fun NewPatchPage(from: String, entry: NavBackStackEntry) {
|
||||
val viewModel = viewModel<NewPatchViewModel>()
|
||||
val snackbarHost = LocalSnackbarHost.current
|
||||
val navController = LocalNavController.current
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
val isCancelled by entry.observeState<Boolean>("isCancelled")
|
||||
LaunchedEffect(Unit) {
|
||||
lspApp.tmpApkDir.listFiles()?.forEach(File::delete)
|
||||
entry.savedStateHandle.getLiveData<AppInfo>("appInfo").observe(lifecycleOwner) {
|
||||
viewModel.configurePatch(it)
|
||||
}
|
||||
|
|
@ -67,9 +73,28 @@ fun NewPatchPage(entry: NavBackStackEntry) {
|
|||
|
||||
Log.d(TAG, "PatchState: ${viewModel.patchState}")
|
||||
if (viewModel.patchState == PatchState.SELECTING) {
|
||||
val storageLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { apks ->
|
||||
if (apks.isEmpty()) {
|
||||
navController.popBackStack()
|
||||
return@rememberLauncherForActivityResult
|
||||
}
|
||||
runBlocking {
|
||||
LSPPackageManager.getAppInfoFromApks(apks)
|
||||
.onSuccess {
|
||||
viewModel.configurePatch(it)
|
||||
}
|
||||
.onFailure {
|
||||
lspApp.globalScope.launch { snackbarHost.showSnackbar(it.message ?: "Unknown error") }
|
||||
navController.popBackStack()
|
||||
}
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
if (isCancelled == true) navController.popBackStack()
|
||||
else navController.navigate(PageList.SelectApps.name + "/false")
|
||||
else when (from) {
|
||||
"storage" -> storageLauncher.launch(arrayOf("application/vnd.android.package-archive"))
|
||||
"applist" -> navController.navigate(PageList.SelectApps.name + "?multiSelect=false")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Scaffold(
|
||||
|
|
@ -173,7 +198,7 @@ private fun PatchOptionsBody(modifier: Modifier) {
|
|||
desc = stringResource(R.string.patch_portable_desc),
|
||||
extraContent = {
|
||||
TextButton(
|
||||
onClick = { navController.navigate(PageList.SelectApps.name + "/true") },
|
||||
onClick = { navController.navigate(PageList.SelectApps.name + "?multiSelect=true") },
|
||||
content = { Text(text = stringResource(R.string.patch_embed_modules), style = MaterialTheme.typography.bodyLarge) }
|
||||
)
|
||||
}
|
||||
|
|
@ -265,7 +290,6 @@ private class PatchLogger(private val logs: MutableList<Pair<Int, String>>) : Lo
|
|||
@Composable
|
||||
private fun DoPatchBody(modifier: Modifier) {
|
||||
val viewModel = viewModel<NewPatchViewModel>()
|
||||
val context = LocalContext.current
|
||||
val snackbarHost = LocalSnackbarHost.current
|
||||
val navController = LocalNavController.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
|
@ -274,14 +298,14 @@ private fun DoPatchBody(modifier: Modifier) {
|
|||
|
||||
LaunchedEffect(Unit) {
|
||||
try {
|
||||
Patcher.patch(context, logger, viewModel.patchOptions)
|
||||
Patcher.patch(logger, viewModel.patchOptions)
|
||||
viewModel.finishPatch()
|
||||
} catch (t: Throwable) {
|
||||
logger.e(t.message.orEmpty())
|
||||
logger.e(t.stackTraceToString())
|
||||
viewModel.failPatch()
|
||||
} finally {
|
||||
viewModel.patchOptions.outputDir.deleteRecursively()
|
||||
lspApp.tmpApkDir.listFiles()?.forEach(File::delete)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -336,14 +360,15 @@ private fun DoPatchBody(modifier: Modifier) {
|
|||
var installing by rememberSaveable { mutableStateOf(false) }
|
||||
if (installing) InstallDialog(viewModel.patchApp) { status, message ->
|
||||
scope.launch {
|
||||
LSPPackageManager.fetchAppList()
|
||||
installing = false
|
||||
if (status == PackageInstaller.STATUS_SUCCESS) {
|
||||
lspApp.globalScope.launch { snackbarHost.showSnackbar(installSuccessfully) }
|
||||
navController.popBackStack()
|
||||
} else {
|
||||
} else if (status != LSPPackageManager.STATUS_USER_CANCELLED) {
|
||||
val result = snackbarHost.showSnackbar(installFailed, copyError)
|
||||
if (result == SnackbarResult.ActionPerformed) {
|
||||
val cm = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val cm = lspApp.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
cm.setPrimaryClip(ClipData.newPlainText("LSPatch", message))
|
||||
}
|
||||
}
|
||||
|
|
@ -382,7 +407,7 @@ private fun DoPatchBody(modifier: Modifier) {
|
|||
Button(
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = {
|
||||
val cm = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val cm = lspApp.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
cm.setPrimaryClip(ClipData.newPlainText("LSPatch", logs.joinToString { it.second + "\n" }))
|
||||
},
|
||||
content = { Text(stringResource(R.string.patch_copy_error)) }
|
||||
|
|
@ -398,20 +423,26 @@ private fun DoPatchBody(modifier: Modifier) {
|
|||
@Composable
|
||||
private fun InstallDialog(patchApp: AppInfo, onFinish: (Int, String?) -> Unit) {
|
||||
val scope = rememberCoroutineScope()
|
||||
var uninstallFirst by remember { mutableStateOf(ShizukuApi.isPackageInstalled(patchApp.app.packageName)) }
|
||||
var uninstallFirst by remember { mutableStateOf(ShizukuApi.isPackageInstalledWithoutPatch(patchApp.app.packageName)) }
|
||||
var installing by remember { mutableStateOf(0) }
|
||||
val doInstall = suspend {
|
||||
Log.i(TAG, "Installing app ${patchApp.app.packageName}")
|
||||
installing = 1
|
||||
val (status, message) = LSPPackageInstaller.install()
|
||||
val (status, message) = LSPPackageManager.install()
|
||||
installing = 0
|
||||
Log.i(TAG, "Installation end: $status, $message")
|
||||
onFinish(status, message)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (!uninstallFirst) {
|
||||
doInstall()
|
||||
}
|
||||
}
|
||||
|
||||
if (uninstallFirst) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { onFinish(-2, "User cancelled") },
|
||||
onDismissRequest = { onFinish(LSPPackageManager.STATUS_USER_CANCELLED, "User cancelled") },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
|
|
@ -419,7 +450,7 @@ private fun InstallDialog(patchApp: AppInfo, onFinish: (Int, String?) -> Unit) {
|
|||
Log.i(TAG, "Uninstalling app ${patchApp.app.packageName}")
|
||||
uninstallFirst = false
|
||||
installing = 2
|
||||
val (status, message) = LSPPackageInstaller.uninstall(patchApp.app.packageName)
|
||||
val (status, message) = LSPPackageManager.uninstall(patchApp.app.packageName)
|
||||
installing = 0
|
||||
Log.i(TAG, "Uninstallation end: $status, $message")
|
||||
if (status == PackageInstaller.STATUS_SUCCESS) {
|
||||
|
|
@ -434,7 +465,7 @@ private fun InstallDialog(patchApp: AppInfo, onFinish: (Int, String?) -> Unit) {
|
|||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = { onFinish(-2, "User cancelled") },
|
||||
onClick = { onFinish(LSPPackageManager.STATUS_USER_CANCELLED, "User cancelled") },
|
||||
content = { Text(stringResource(android.R.string.cancel)) }
|
||||
)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -44,13 +44,16 @@ enum class PageList(
|
|||
body = { SettingsPage() }
|
||||
),
|
||||
NewPatch(
|
||||
body = { NewPatchPage(this) }
|
||||
arguments = listOf(
|
||||
navArgument("from") { type = NavType.StringType }
|
||||
),
|
||||
body = { NewPatchPage(arguments!!.getString("from")!!, this) }
|
||||
),
|
||||
SelectApps(
|
||||
arguments = listOf(
|
||||
navArgument("multiSelect") { type = NavType.BoolType }
|
||||
),
|
||||
body = { SelectAppsPage(this) }
|
||||
body = { SelectAppsPage(arguments!!.getBoolean("multiSelect")) }
|
||||
);
|
||||
|
||||
val title: String
|
||||
|
|
|
|||
|
|
@ -28,16 +28,15 @@ import org.lsposed.lspatch.ui.component.SearchAppBar
|
|||
import org.lsposed.lspatch.ui.util.LocalNavController
|
||||
import org.lsposed.lspatch.ui.util.observeState
|
||||
import org.lsposed.lspatch.ui.util.setState
|
||||
import org.lsposed.lspatch.ui.viewmodel.AppInfo
|
||||
import org.lsposed.lspatch.ui.viewmodel.SelectAppsViewModel
|
||||
import org.lsposed.lspatch.util.LSPPackageManager
|
||||
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SelectAppsPage(entry: NavBackStackEntry) {
|
||||
fun SelectAppsPage(multiSelect: Boolean) {
|
||||
val viewModel = viewModel<SelectAppsViewModel>()
|
||||
val navController = LocalNavController.current
|
||||
val multiSelect = entry.arguments?.get("multiSelect") as? Boolean
|
||||
?: throw IllegalArgumentException("multiSelect is null")
|
||||
|
||||
var searchPackage by remember { mutableStateOf("") }
|
||||
val filter: (AppInfo) -> Boolean = {
|
||||
|
|
@ -113,7 +112,7 @@ private fun SingleSelect() {
|
|||
) {
|
||||
AppItem(
|
||||
modifier = Modifier.animateItemPlacement(spring(stiffness = Spring.StiffnessLow)),
|
||||
icon = viewModel.getIcon(it),
|
||||
icon = LSPPackageManager.getIcon(it),
|
||||
label = it.label,
|
||||
packageName = it.app.packageName,
|
||||
onClick = {
|
||||
|
|
@ -140,7 +139,7 @@ private fun MultiSelect() {
|
|||
val checked = selected!!.contains(it)
|
||||
AppItem(
|
||||
modifier = Modifier.animateItemPlacement(spring(stiffness = Spring.StiffnessLow)),
|
||||
icon = viewModel.getIcon(it),
|
||||
icon = LSPPackageManager.getIcon(it),
|
||||
label = it.label,
|
||||
packageName = it.app.packageName,
|
||||
onClick = {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
package org.lsposed.lspatch.ui.viewmodel
|
||||
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.google.gson.Gson
|
||||
import org.lsposed.lspatch.share.PatchConfig
|
||||
import org.lsposed.lspatch.util.LSPPackageManager
|
||||
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
|
||||
|
||||
private const val TAG = "ManageViewModel"
|
||||
|
||||
class ManageViewModel : ViewModel() {
|
||||
|
||||
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)
|
||||
appInfo to Gson().fromJson(json, PatchConfig::class.java)
|
||||
}
|
||||
}.also {
|
||||
Log.d(TAG, "Loaded ${it.size} patched apps")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.lifecycle.ViewModel
|
||||
import org.lsposed.lspatch.Patcher
|
||||
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
|
||||
|
||||
class NewPatchViewModel : ViewModel() {
|
||||
|
||||
|
|
@ -27,6 +28,7 @@ class NewPatchViewModel : ViewModel() {
|
|||
private set
|
||||
lateinit var embeddedModules: SnapshotStateList<AppInfo>
|
||||
lateinit var patchOptions: Patcher.Options
|
||||
private set
|
||||
|
||||
fun configurePatch(app: AppInfo) {
|
||||
patchApp = app
|
||||
|
|
|
|||
|
|
@ -1,31 +1,17 @@
|
|||
package org.lsposed.lspatch.ui.viewmodel
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.lsposed.lspatch.lspApp
|
||||
import java.text.Collator
|
||||
import java.util.*
|
||||
import org.lsposed.lspatch.util.LSPPackageManager
|
||||
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
|
||||
|
||||
private const val TAG = "SelectAppViewModel"
|
||||
|
||||
@Parcelize
|
||||
class AppInfo(val app: ApplicationInfo, val label: String) : Parcelable
|
||||
|
||||
private var appList = listOf<AppInfo>()
|
||||
private val appIcon = mutableMapOf<String, Drawable>()
|
||||
|
||||
class SelectAppsViewModel : ViewModel() {
|
||||
|
||||
init {
|
||||
|
|
@ -40,28 +26,13 @@ class SelectAppsViewModel : ViewModel() {
|
|||
|
||||
fun filterAppList(refresh: Boolean, filter: (AppInfo) -> Boolean) {
|
||||
viewModelScope.launch {
|
||||
if (appList.isEmpty() || refresh) refreshAppList()
|
||||
filteredList = appList.filter(filter)
|
||||
}
|
||||
}
|
||||
|
||||
fun getIcon(appInfo: AppInfo) = appIcon[appInfo.app.packageName]!!
|
||||
|
||||
private suspend fun refreshAppList() {
|
||||
Log.d(TAG, "Start refresh apps")
|
||||
if (LSPPackageManager.appList.isEmpty() || refresh) {
|
||||
isRefreshing = true
|
||||
val collection = mutableListOf<AppInfo>()
|
||||
withContext(Dispatchers.IO) {
|
||||
val pm = lspApp.packageManager
|
||||
pm.getInstalledApplications(PackageManager.GET_META_DATA).forEach {
|
||||
val label = pm.getApplicationLabel(it)
|
||||
appIcon[it.packageName] = pm.getApplicationIcon(it)
|
||||
collection.add(AppInfo(it, label.toString()))
|
||||
}
|
||||
collection.sortWith(compareBy(Collator.getInstance(Locale.getDefault())) { it.label })
|
||||
}
|
||||
appList = collection
|
||||
LSPPackageManager.fetchAppList()
|
||||
isRefreshing = false
|
||||
Log.d(TAG, "Refreshed ${appList.size} apps")
|
||||
}
|
||||
filteredList = LSPPackageManager.appList.filter(filter)
|
||||
Log.d(TAG, "Filtered ${filteredList.size} apps")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,64 @@
|
|||
package org.lsposed.lspatch.util
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.core.net.toUri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import hidden.HiddenApiBridge
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.lsposed.lspatch.Constants.PATCH_FILE_SUFFIX
|
||||
import org.lsposed.lspatch.Constants.PREFS_STORAGE_DIRECTORY
|
||||
import org.lsposed.lspatch.lspApp
|
||||
import org.lsposed.patch.util.ManifestParser
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.text.Collator
|
||||
import java.util.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.zip.ZipFile
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
object LSPPackageInstaller {
|
||||
object LSPPackageManager {
|
||||
|
||||
const val TAG = "LSPPackageManager"
|
||||
|
||||
@Parcelize
|
||||
class AppInfo(val app: ApplicationInfo, val label: String) : Parcelable
|
||||
|
||||
const val STATUS_USER_CANCELLED = -2
|
||||
|
||||
var appList by mutableStateOf(listOf<AppInfo>())
|
||||
private set
|
||||
|
||||
private val appIcon = mutableMapOf<String, Drawable>()
|
||||
|
||||
suspend fun fetchAppList() {
|
||||
withContext(Dispatchers.IO) {
|
||||
val pm = lspApp.packageManager
|
||||
val collection = mutableListOf<AppInfo>()
|
||||
pm.getInstalledApplications(PackageManager.GET_META_DATA).forEach {
|
||||
val label = pm.getApplicationLabel(it)
|
||||
collection.add(AppInfo(it, label.toString()))
|
||||
appIcon[it.packageName] = pm.getApplicationIcon(it)
|
||||
}
|
||||
collection.sortWith(compareBy(Collator.getInstance(Locale.getDefault())) { it.label })
|
||||
appList = collection
|
||||
}
|
||||
}
|
||||
|
||||
fun getIcon(appInfo: AppInfo) = appIcon[appInfo.app.packageName]!!
|
||||
|
||||
suspend fun install(): Pair<Int, String?> {
|
||||
var status = PackageInstaller.STATUS_FAILURE
|
||||
|
|
@ -31,11 +75,12 @@ object LSPPackageInstaller {
|
|||
?: throw IOException("Uri is null")
|
||||
val root = DocumentFile.fromTreeUri(lspApp, uri)
|
||||
?: throw IOException("DocumentFile is null")
|
||||
root.listFiles().forEach { apk ->
|
||||
val input = lspApp.contentResolver.openInputStream(apk.uri)
|
||||
root.listFiles().forEach { file ->
|
||||
if (file.name?.endsWith(PATCH_FILE_SUFFIX) != true) return@forEach
|
||||
val input = lspApp.contentResolver.openInputStream(file.uri)
|
||||
?: throw IOException("Cannot open input stream")
|
||||
input.use {
|
||||
session.openWrite(apk.name!!, 0, input.available().toLong()).use { output ->
|
||||
session.openWrite(file.name!!, 0, input.available().toLong()).use { output ->
|
||||
input.copyTo(output)
|
||||
session.fsync(output)
|
||||
}
|
||||
|
|
@ -94,4 +139,43 @@ object LSPPackageInstaller {
|
|||
}
|
||||
return Pair(status, message)
|
||||
}
|
||||
|
||||
suspend fun getAppInfoFromApks(apks: List<Uri>): Result<AppInfo> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val app = ApplicationInfo()
|
||||
if (apks.size > 1) app.splitSourceDirs = Array<String?>(apks.size - 1) { null }
|
||||
apks.forEachIndexed { index, uri ->
|
||||
val src = DocumentFile.fromSingleUri(lspApp, uri)
|
||||
?: throw IOException("DocumentFile is null")
|
||||
val dst = lspApp.tmpApkDir.resolve(src.name!!)
|
||||
val input = lspApp.contentResolver.openInputStream(uri)
|
||||
?: throw IOException("InputStream is null")
|
||||
input.use {
|
||||
dst.outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
ZipFile(dst).use { zip ->
|
||||
val entry = zip.getEntry("AndroidManifest.xml")
|
||||
?: throw IOException("AndroidManifest.xml is not found")
|
||||
zip.getInputStream(entry).use {
|
||||
val info = ManifestParser.parseManifestFile(it)
|
||||
if (app.packageName != null && app.packageName != info.packageName) {
|
||||
throw IOException("Selected apks are not of the same app")
|
||||
}
|
||||
app.packageName = info.packageName
|
||||
}
|
||||
}
|
||||
if (index == 0) app.sourceDir = dst.absolutePath
|
||||
else app.splitSourceDirs[index - 1] = dst.absolutePath
|
||||
}
|
||||
AppInfo(app, app.packageName)
|
||||
}.recoverCatching { t ->
|
||||
lspApp.tmpApkDir.listFiles()?.forEach(File::delete)
|
||||
Log.e(TAG, "Failed to load apks", t)
|
||||
throw t
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -63,8 +63,9 @@ object ShizukuApi {
|
|||
return constructor.newInstance(iSession)
|
||||
}
|
||||
|
||||
fun isPackageInstalled(packageName: String): Boolean {
|
||||
return iPackageManager.getPackageInfo(packageName, 0, 0 /* TODO: userId */) != null
|
||||
fun isPackageInstalledWithoutPatch(packageName: String): Boolean {
|
||||
val app = iPackageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA, 0 /* TODO: userId */)
|
||||
return (app != null) && (app.metaData?.containsKey("lspatch") != true)
|
||||
}
|
||||
|
||||
fun uninstallPackage(packageName: String, intentSender: IntentSender) {
|
||||
|
|
|
|||
|
|
@ -23,33 +23,32 @@
|
|||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group
|
||||
android:scaleX="0.12397959"
|
||||
android:scaleY="0.12397959"
|
||||
android:translateX="-20.387754"
|
||||
android:translateY="13.5">
|
||||
android:scaleX="0.28265625"
|
||||
android:scaleY="0.28265625"
|
||||
android:translateX="17.82"
|
||||
android:translateY="17.82">
|
||||
<path
|
||||
android:fillColor="#a697e8"
|
||||
android:pathData="M280,0h640v640h-640z" />
|
||||
<group>
|
||||
<clip-path android:pathData="M280,0h640v640h-640z" />
|
||||
android:fillColor="#c9dc87"
|
||||
android:pathData="M0,0h256v256h-256z" />
|
||||
<path
|
||||
android:fillColor="#e4e0f7"
|
||||
android:pathData="M600,1080m-600,0a600,600 0,1 1,1200 0a600,600 0,1 1,-1200 0" />
|
||||
</group>
|
||||
android:fillAlpha="0.7"
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M256,256v-27.38c-26.01,-15.34 -73.6,-25.62 -128,-25.62S26.01,213.28 0,228.62v27.38H256Z"
|
||||
android:strokeAlpha="0.7" />
|
||||
<path
|
||||
android:fillColor="#607d8b"
|
||||
android:pathData="M504.4,556.3l3.8,-21.8 -7.2,5.7 -0.7,-0.8c2.9,-3 5.8,-6.3 8.7,-9.7l0.2,-1.1 0.5,0.3c5,-5.9 9.9,-12.1 14.6,-18.6l5.1,6.2c-1.3,1.3 -2.7,2.4 -4.1,3.5h16.2c0.9,-0.9 1.8,-1.8 2.6,-2.7l4.3,6.5c-4.7,3 -9.6,5.8 -14.7,8.4h14.3l6.7,-3 -4.6,25.9 -5,-5.4h-9.7l2,2.2 -2.8,16.2h5.9l-0.1,0.3h7.8l10.4,-14.8 1.4,1.4 -12.9,18 -0.2,-0.3 -0.1,0.3h-11.3l0.1,-0.3c-3.5,0.5 -6.5,2.4 -9.1,5.7l1,-5.7 -0.3,0.3 4.1,-23.2h-0.5c-0.7,3.8 -2.7,6.9 -6,9.4 -4.3,3.8 -9.1,7.2 -14.5,10.2s-10.6,5.7 -15.7,8.1l-0.5,-1.6c4.4,-2.3 9.1,-5.3 14.2,-8.8 5.1,-3.5 9.3,-7 12.6,-10.6 1.8,-2 3,-4.2 3.6,-6.7h-12.9l-0.4,2.2 -7,4.6ZM512.3,546.6h12.9l2,-11.6h-12.9l-2,11.6ZM514.8,532.1h14.3c3.2,-3 6.4,-6.1 9.4,-9.2h-16.7c-3,2.7 -6.1,5.3 -9.2,7.8l2.2,1.4ZM532.7,539.4l-1.3,7.3h0.5l0.1,-0.5 0.4,0.5h12.9l2,-11.6h-18.6l3.8,4.3Z" />
|
||||
android:fillColor="#0e7c61"
|
||||
android:pathData="M86.37,232.77l1.67,-9.49 -3.13,2.46 -0.29,-0.35c1.25,-1.33 2.51,-2.73 3.79,-4.22l0.08,-0.47 0.21,0.12c2.17,-2.58 4.29,-5.27 6.35,-8.09l2.22,2.7c-0.57,0.55 -1.16,1.05 -1.79,1.52h7.03c0.38,-0.39 0.76,-0.78 1.14,-1.17l1.85,2.81c-2.03,1.33 -4.16,2.54 -6.38,3.63h6.21l2.92,-1.29 -1.98,11.25 -2.17,-2.34h-4.22l0.89,0.94 -1.24,7.03h2.58l-0.02,0.12h3.4l4.53,-6.45 0.6,0.59 -5.6,7.85 -0.1,-0.12 -0.02,0.12h-4.92l0.02,-0.12c-1.53,0.23 -2.84,1.05 -3.95,2.46l0.43,-2.46 -0.14,0.12 1.78,-10.08h-0.23c-0.29,1.64 -1.16,3.01 -2.6,4.1 -1.85,1.64 -3.95,3.13 -6.29,4.45 -2.34,1.33 -4.62,2.5 -6.83,3.52l-0.23,-0.7c1.9,-1.02 3.96,-2.29 6.18,-3.81 2.22,-1.52 4.06,-3.07 5.5,-4.63 0.78,-0.86 1.3,-1.83 1.57,-2.93h-5.62l-0.17,0.94 -3.05,1.99ZM89.81,228.55h5.62l0.89,-5.04h-5.62l-0.89,5.04ZM90.93,222.22h6.21c1.41,-1.33 2.77,-2.66 4.1,-3.98h-7.27c-1.3,1.17 -2.63,2.31 -4,3.4l0.95,0.59ZM98.69,225.38l-0.56,3.16h0.23l0.04,-0.23 0.19,0.23h5.62l0.89,-5.04h-8.09l1.66,1.88Z" />
|
||||
<path
|
||||
android:fillColor="#607d8b"
|
||||
android:pathData="M573.1,523.2h16.2c9.5,0 16.8,3.3 14.9,14 -1.8,10.3 -10.4,14.8 -20,14.8h-6l-2.8,16.2h-10.2l7.9,-44.9ZM584.9,543.9c5.3,0 8.6,-2.3 9.4,-6.7 0.8,-4.4 -1.8,-5.9 -7.2,-5.9h-5.2l-2.2,12.6h5.2ZM582.9,549.3l8.2,-6.5 9.9,25.4h-11.4l-6.7,-18.9Z" />
|
||||
android:fillColor="#0e7c61"
|
||||
android:pathData="M116.29,218.38h7.04c4.15,0 7.29,1.44 6.47,6.09 -0.79,4.49 -4.53,6.43 -8.68,6.43h-2.62l-1.24,7.04h-4.42l3.45,-19.55ZM121.38,227.38c2.33,0 3.75,-1 4.09,-2.92 0.34,-1.93 -0.79,-2.58 -3.12,-2.58h-2.26l-0.97,5.5h2.26ZM120.52,229.72l3.56,-2.83 4.29,11.03h-4.95l-2.9,-8.2Z" />
|
||||
<path
|
||||
android:fillColor="#607d8b"
|
||||
android:pathData="M607,551.1c2,-11.4 11.2,-17.9 19.8,-17.9s15.5,6.6 13.5,17.9c-2,11.4 -11.2,17.9 -19.8,17.9s-15.5,-6.6 -13.5,-17.9ZM629.9,551.1c1,-5.9 -0.4,-9.8 -4.6,-9.8s-6.9,3.8 -8,9.8c-1,5.9 0.4,9.7 4.6,9.7s6.9,-3.8 8,-9.7Z" />
|
||||
android:fillColor="#0e7c61"
|
||||
android:pathData="M131.02,230.49c0.87,-4.95 4.86,-7.81 8.63,-7.81s6.75,2.86 5.87,7.81c-0.87,4.94 -4.86,7.79 -8.62,7.79s-6.75,-2.86 -5.88,-7.79ZM141,230.49c0.45,-2.58 -0.16,-4.25 -1.98,-4.25s-3.03,1.67 -3.48,4.25c-0.45,2.58 0.16,4.24 1.98,4.24s3.02,-1.66 3.48,-4.24Z" />
|
||||
<path
|
||||
android:fillColor="#607d8b"
|
||||
android:pathData="M645.2,551.1c2,-11.4 11.2,-17.9 19.8,-17.9s15.5,6.6 13.5,17.9c-2,11.4 -11.2,17.9 -19.8,17.9s-15.5,-6.6 -13.5,-17.9ZM668.2,551.1c1,-5.9 -0.4,-9.8 -4.6,-9.8s-6.9,3.8 -8,9.8c-1,5.9 0.4,9.7 4.6,9.7s6.9,-3.8 8,-9.7Z" />
|
||||
android:fillColor="#0e7c61"
|
||||
android:pathData="M147.67,230.49c0.87,-4.95 4.86,-7.81 8.63,-7.81s6.75,2.86 5.87,7.81c-0.87,4.94 -4.86,7.79 -8.62,7.79s-6.75,-2.86 -5.88,-7.79ZM157.65,230.49c0.45,-2.58 -0.16,-4.25 -1.98,-4.25s-3.03,1.67 -3.48,4.25c-0.45,2.58 0.16,4.24 1.98,4.24s3.02,-1.66 3.48,-4.24Z" />
|
||||
<path
|
||||
android:fillColor="#607d8b"
|
||||
android:pathData="M684.2,555.7l2.4,-13.8h-4.7l1.3,-7.6 5.3,-0.4 2.8,-9.1h8.4l-1.6,9.1h8.2l-1.4,7.9h-8.2l-2.4,13.7c-0.7,3.9 0.8,5.4 3.5,5.4 1.1,0 2.4,-0.3 3.4,-0.6l0.3,7.4c-1.9,0.6 -4.5,1.2 -7.8,1.2 -8.4,0 -10.9,-5.3 -9.5,-13.3Z" />
|
||||
android:fillColor="#0e7c61"
|
||||
android:pathData="M164.64,232.52l1.06,-6.01h-2.03l0.58,-3.29 2.31,-0.17 1.21,-3.95h3.65l-0.7,3.95h3.57l-0.61,3.46h-3.57l-1.05,5.96c-0.3,1.69 0.35,2.36 1.51,2.36 0.49,0 1.05,-0.14 1.46,-0.28l0.13,3.2c-0.83,0.25 -1.96,0.54 -3.4,0.54 -3.68,0 -4.73,-2.32 -4.12,-5.77Z" />
|
||||
</group>
|
||||
</vector>
|
||||
|
|
|
|||
|
|
@ -23,42 +23,41 @@
|
|||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group
|
||||
android:scaleX="0.12397959"
|
||||
android:scaleY="0.12397959"
|
||||
android:translateX="-20.387754"
|
||||
android:translateY="13.5">
|
||||
android:scaleX="0.28265625"
|
||||
android:scaleY="0.28265625"
|
||||
android:translateX="17.82"
|
||||
android:translateY="17.82">
|
||||
<path
|
||||
android:fillColor="#a697e8"
|
||||
android:pathData="M280,0h640v640h-640z" />
|
||||
<group>
|
||||
<clip-path android:pathData="M280,0h640v640h-640z" />
|
||||
android:fillColor="#c9dc87"
|
||||
android:pathData="M0,0h256v256h-256z" />
|
||||
<path
|
||||
android:fillColor="#e4e0f7"
|
||||
android:pathData="M600,1080m-600,0a600,600 0,1 1,1200 0a600,600 0,1 1,-1200 0" />
|
||||
</group>
|
||||
android:fillAlpha="0.7"
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M256,256v-27.38c-26.01,-15.34 -73.6,-25.62 -128,-25.62S26.01,213.28 0,228.62v27.38H256Z"
|
||||
android:strokeAlpha="0.7" />
|
||||
<path
|
||||
android:fillColor="#607d8b"
|
||||
android:pathData="M485.6,524.2h14.1c8.8,0 15.4,3.1 15.4,11.5 0,11.8 -9.6,17 -20,17h-5.7l-3.3,16.5h-9.4l9,-44.9ZM495.6,545.2c6.7,0 10.1,-3.2 10.1,-8.1 0,-3.8 -2.7,-5.4 -7.5,-5.4h-4.7l-2.6,13.5h4.8ZM494,550.5l6.8,-6.1 9.7,24.7h-10l-6.6,-18.7Z" />
|
||||
android:fillColor="#0e7c61"
|
||||
android:pathData="M78.19,218.79h6.13c3.84,0 6.69,1.36 6.69,5 0,5.12 -4.17,7.38 -8.73,7.38h-2.48l-1.42,7.17h-4.11l3.91,-19.55ZM82.56,227.91c2.91,0 4.39,-1.4 4.39,-3.52 0,-1.64 -1.16,-2.35 -3.26,-2.35h-2.06l-1.15,5.88h2.08ZM81.88,230.21l2.97,-2.63 4.22,10.76h-4.33l-2.85,-8.12Z" />
|
||||
<path
|
||||
android:fillColor="#607d8b"
|
||||
android:pathData="M517,555.9c0,-13.2 9.8,-21.8 19.2,-21.8 8,0 13.3,5.6 13.3,14.1 0,13.2 -9.8,21.8 -19.2,21.8 -8,0 -13.3,-5.6 -13.3,-14.1ZM539.9,548.4c0,-4.2 -1.6,-6.7 -4.8,-6.7 -4.6,0 -8.6,6 -8.6,14 0,4.2 1.6,6.7 4.8,6.7 4.6,0 8.6,-6 8.6,-14Z" />
|
||||
android:fillColor="#0e7c61"
|
||||
android:pathData="M91.9,232.57c0,-5.74 4.24,-9.47 8.34,-9.47 3.47,0 5.78,2.44 5.78,6.13 0,5.74 -4.24,9.47 -8.34,9.47 -3.47,0 -5.78,-2.44 -5.78,-6.13ZM101.87,229.32c0,-1.82 -0.7,-2.91 -2.08,-2.91 -2,0 -3.73,2.62 -3.73,6.09 0,1.82 0.7,2.91 2.08,2.91 2,0 3.73,-2.61 3.73,-6.09Z" />
|
||||
<path
|
||||
android:fillColor="#607d8b"
|
||||
android:pathData="M553.8,555.9c0,-13.2 9.8,-21.8 19.2,-21.8 8,0 13.3,5.6 13.3,14.1 0,13.2 -9.8,21.8 -19.2,21.8 -8,0 -13.3,-5.6 -13.3,-14.1ZM576.7,548.4c0,-4.2 -1.6,-6.7 -4.8,-6.7 -4.6,0 -8.6,6 -8.6,14 0,4.2 1.6,6.7 4.8,6.7 4.6,0 8.6,-6 8.6,-14Z" />
|
||||
android:fillColor="#0e7c61"
|
||||
android:pathData="M107.89,232.57c0,-5.74 4.24,-9.47 8.34,-9.47 3.47,0 5.78,2.44 5.78,6.13 0,5.74 -4.24,9.47 -8.34,9.47 -3.47,0 -5.78,-2.44 -5.78,-6.13ZM117.86,229.32c0,-1.82 -0.7,-2.91 -2.08,-2.91 -2,0 -3.73,2.62 -3.73,6.09 0,1.82 0.7,2.91 2.08,2.91 2,0 3.73,-2.61 3.73,-6.09Z" />
|
||||
<path
|
||||
android:fillColor="#607d8b"
|
||||
android:pathData="M592.8,561c0,-1.5 0.3,-2.9 0.5,-4.3l3,-14.3h-4.6l1.4,-7 5,-0.4 2.9,-8.9h7.9l-1.7,8.9h7.7l-1.4,7.4h-8l-3,14.7c-0.1,0.8 -0.2,1.5 -0.2,2.3 0,2.3 1.1,3.3 3.6,3.3 1,0 1.9,-0.3 2.8,-0.8l1.7,6.6c-1.8,0.7 -4.5,1.5 -8,1.5 -6.9,0 -9.7,-3.8 -9.7,-9Z" />
|
||||
android:fillColor="#0e7c61"
|
||||
android:pathData="M124.86,234.76c0,-0.64 0.11,-1.25 0.22,-1.87l1.3,-6.23h-1.98l0.61,-3.03 2.19,-0.17 1.26,-3.89h3.44l-0.74,3.89h3.37l-0.62,3.2h-3.47l-1.3,6.39c-0.06,0.36 -0.07,0.67 -0.07,0.99 0,0.98 0.48,1.46 1.56,1.46 0.42,0 0.83,-0.15 1.21,-0.34l0.75,2.88c-0.79,0.3 -1.96,0.66 -3.49,0.66 -3,0 -4.21,-1.66 -4.21,-3.94Z" />
|
||||
<path
|
||||
android:fillColor="#607d8b"
|
||||
android:pathData="M616.3,563.3c0,-1.2 0.1,-2.5 0.5,-4.2l7.7,-38.3h9.4l-7.8,38.6c-0.1,0.7 -0.1,1 -0.1,1.3 0,1.2 0.6,1.6 1.3,1.6 0.4,0 0.6,0 1.2,-0.2l-0.3,7c-1.1,0.4 -2.8,0.8 -5,0.8 -4.9,0 -6.9,-2.5 -6.9,-6.7Z" />
|
||||
android:fillColor="#0e7c61"
|
||||
android:pathData="M135.1,235.79c0,-0.53 0.06,-1.07 0.22,-1.82l3.33,-16.65h4.1l-3.38,16.81c-0.06,0.3 -0.06,0.43 -0.06,0.57 0,0.51 0.27,0.69 0.57,0.69 0.17,0 0.28,0 0.53,-0.07l-0.13,3.04c-0.5,0.17 -1.22,0.33 -2.17,0.33 -2.13,0 -3.01,-1.1 -3.01,-2.91Z" />
|
||||
<path
|
||||
android:fillColor="#607d8b"
|
||||
android:pathData="M653.8,534.1c8.2,0 11.1,5.7 11.1,12.8 0,3.2 -1.2,6.8 -1.7,7.8h-19.5c-0.2,5.7 3.2,8.1 7.7,8.1 2.1,0 4.6,-1.2 6.3,-2.4l3.3,5.9c-2.7,1.9 -7,3.6 -12.3,3.6 -8.3,0 -14,-5.5 -14,-14.6 0,-12.7 9.9,-21.2 19,-21.2ZM656.9,549c0.2,-0.7 0.3,-1.6 0.3,-2.5 0,-2.8 -1.2,-5.1 -4.6,-5.1 -3.3,0 -6.6,2.6 -8.2,7.6h12.4Z" />
|
||||
android:fillColor="#0e7c61"
|
||||
android:pathData="M151.39,223.1c3.55,0 4.85,2.49 4.85,5.58 0,1.4 -0.53,2.94 -0.76,3.39h-8.48c-0.1,2.48 1.39,3.53 3.35,3.53 0.92,0 2,-0.51 2.73,-1.04l1.46,2.59c-1.19,0.84 -3.04,1.56 -5.34,1.56 -3.6,0 -6.09,-2.39 -6.09,-6.38 0,-5.51 4.31,-9.23 8.27,-9.23ZM152.75,229.56c0.07,-0.3 0.13,-0.7 0.13,-1.11 0,-1.2 -0.5,-2.2 -2,-2.2 -1.42,0 -2.87,1.12 -3.55,3.31h5.42Z" />
|
||||
<path
|
||||
android:fillColor="#607d8b"
|
||||
android:pathData="M666.4,563.4l5.5,-4.5c2.4,2.9 4.9,4.2 7.4,4.2s4.8,-1.4 4.8,-3.2c0,-2 -2,-2.9 -5.9,-5.1 -3.9,-2.2 -6.9,-5.2 -6.9,-9.5 0,-6.4 5.9,-11.1 13.3,-11.1 4.7,0 8.4,2.3 11.2,5.2l-5.1,4.9c-1.7,-1.7 -3.7,-3.1 -6.1,-3.1 -2.6,0 -4.4,1.5 -4.4,3.3 0,2.2 2.9,3.2 5.6,4.7 4.1,2.2 7.2,4.9 7.2,9.6 0,6.7 -6,11.2 -14.2,11.2 -4.1,0 -9.4,-2.4 -12.3,-6.6Z" />
|
||||
android:fillColor="#0e7c61"
|
||||
android:pathData="M156.88,235.81l2.39,-1.96c1.03,1.26 2.13,1.85 3.21,1.85s2.07,-0.61 2.07,-1.41c0,-0.85 -0.87,-1.24 -2.56,-2.2 -1.69,-0.95 -3.01,-2.25 -3.01,-4.14 0,-2.79 2.58,-4.85 5.76,-4.85 2.05,0 3.67,1.01 4.88,2.27l-2.23,2.13c-0.73,-0.74 -1.6,-1.35 -2.66,-1.35 -1.13,0 -1.9,0.64 -1.9,1.42 0,0.95 1.26,1.38 2.45,2.06 1.77,0.96 3.12,2.14 3.12,4.18 0,2.93 -2.59,4.89 -6.19,4.89 -1.8,0 -4.11,-1.04 -5.33,-2.89Z" />
|
||||
<path
|
||||
android:fillColor="#607d8b"
|
||||
android:pathData="M695.6,563.4l5.5,-4.5c2.4,2.9 4.9,4.2 7.4,4.2s4.8,-1.4 4.8,-3.2c0,-2 -2,-2.9 -5.9,-5.1 -3.9,-2.2 -6.9,-5.2 -6.9,-9.5 0,-6.4 5.9,-11.1 13.3,-11.1 4.7,0 8.4,2.3 11.2,5.2l-5.1,4.9c-1.7,-1.7 -3.7,-3.1 -6.1,-3.1 -2.6,0 -4.4,1.5 -4.4,3.3 0,2.2 2.9,3.2 5.6,4.7 4.1,2.2 7.2,4.9 7.2,9.6 0,6.7 -6,11.2 -14.2,11.2 -4.1,0 -9.4,-2.4 -12.3,-6.6Z" />
|
||||
android:fillColor="#0e7c61"
|
||||
android:pathData="M169.6,235.81l2.39,-1.96c1.03,1.26 2.13,1.85 3.21,1.85s2.07,-0.61 2.07,-1.41c0,-0.85 -0.87,-1.24 -2.56,-2.2 -1.69,-0.95 -3.01,-2.25 -3.01,-4.14 0,-2.79 2.58,-4.85 5.76,-4.85 2.05,0 3.67,1.01 4.88,2.27l-2.23,2.13c-0.73,-0.74 -1.6,-1.35 -2.66,-1.35 -1.13,0 -1.9,0.64 -1.9,1.42 0,0.95 1.26,1.38 2.45,2.06 1.77,0.96 3.12,2.14 3.12,4.18 0,2.93 -2.59,4.89 -6.19,4.89 -1.8,0 -4.11,-1.04 -5.33,-2.89Z" />
|
||||
</group>
|
||||
</vector>
|
||||
|
|
|
|||
|
|
@ -18,19 +18,17 @@
|
|||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group
|
||||
android:scaleX="1.2375"
|
||||
android:scaleY="1.2375"
|
||||
android:translateX="24.3"
|
||||
android:translateY="24.3">
|
||||
android:scaleX="0.27421874"
|
||||
android:scaleY="0.27421874"
|
||||
android:translateX="18.9"
|
||||
android:translateY="18.9">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M24.1,27.6l-6.7,6.7c-0.4,0.5 -1,0.7 -1.6,0.7c-0.6,0 -1.2,-0.2 -1.6,-0.7l-4.4,-4.4C9.3,29.4 9,28.9 9,28.3c0,-0.6 0.2,-1.2 0.7,-1.6l6.6,-6.7l-6.6,-6.7c-0.4,-0.4 -0.7,-0.9 -0.7,-1.5c-0.1,-0.6 0.1,-1.1 0.5,-1.5l4.6,-4.6c0.4,-0.4 1,-0.7 1.6,-0.7c0.6,0 1.2,0.2 1.6,0.7l6.7,6.7l6.7,-6.7c0.4,-0.4 1,-0.7 1.6,-0.7c0.6,0 1.2,0.2 1.6,0.7l4.4,4.4c0.4,0.4 0.6,1 0.7,1.6c0,0.6 -0.2,1.2 -0.7,1.6L31.7,20l6.7,6.7c0.4,0.4 0.7,1 0.7,1.6c0,0.6 -0.2,1.2 -0.7,1.6l-4.6,4.6c-0.4,0.4 -0.9,0.6 -1.5,0.5c-0.6,-0.1 -1.1,-0.3 -1.5,-0.7L24.1,27.6zM24.1,18.7c0.4,0 0.8,-0.1 1,-0.4c0.3,-0.3 0.4,-0.6 0.4,-1s-0.1,-0.8 -0.4,-1c-0.3,-0.3 -0.6,-0.4 -1,-0.4c-0.4,0 -0.8,0.1 -1,0.4c-0.3,0.3 -0.4,0.6 -0.4,1c0,0.4 0.1,0.8 0.4,1C23.3,18.5 23.6,18.7 24.1,18.7zM17.8,18.5l4.7,-4.7l-6.7,-6.7c0,0 0,0 0,0c0,0 0,0 0,0l-4.6,4.6c0,0 0,0 0,0c0,0 0,0 0,0L17.8,18.5zM21.3,21.4c0.4,0 0.8,-0.1 1,-0.4s0.4,-0.6 0.4,-1s-0.1,-0.8 -0.4,-1s-0.6,-0.4 -1,-0.4c-0.4,0 -0.8,0.1 -1,0.4c-0.3,0.3 -0.4,0.6 -0.4,1s0.1,0.8 0.4,1C20.5,21.3 20.9,21.4 21.3,21.4zM24.1,24.1c0.4,0 0.8,-0.1 1,-0.4s0.4,-0.6 0.4,-1s-0.1,-0.8 -0.4,-1s-0.6,-0.4 -1,-0.4c-0.4,0 -0.8,0.1 -1,0.4c-0.3,0.3 -0.4,0.6 -0.4,1c0,0.4 0.1,0.8 0.4,1C23.3,24 23.6,24.1 24.1,24.1zM26.8,21.4c0.4,0 0.8,-0.1 1,-0.4c0.3,-0.3 0.4,-0.6 0.4,-1s-0.1,-0.8 -0.4,-1c-0.3,-0.3 -0.6,-0.4 -1,-0.4s-0.8,0.1 -1,0.4c-0.3,0.3 -0.4,0.6 -0.4,1s0.1,0.8 0.4,1C26,21.3 26.3,21.4 26.8,21.4zM25.5,26.1l6.7,6.7l0,0l0,0l4.6,-4.6l0,0l0,0l-6.7,-6.7L25.5,26.1zM19.4,15.4L19.4,15.4L19.4,15.4L19.4,15.4L19.4,15.4zM28.6,24.6C28.6,24.6 28.6,24.6 28.6,24.6S28.6,24.6 28.6,24.6S28.6,24.6 28.6,24.6S28.6,24.6 28.6,24.6z"
|
||||
tools:ignore="VectorPath" />
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M167.13,107.36l27.34,-27.34c2.65,-2.67 2.65,-6.97 0,-9.64l-29.8,-29.39c-2.67,-2.65 -6.97,-2.65 -9.64,0l-27.34,27.34 -27.34,-27.34c-1.22,-1.21 -2.86,-1.92 -4.58,-1.98 -1.79,0 -3.51,0.72 -4.79,1.98l-29.67,29.67c-2.47,2.63 -2.47,6.73 0,9.37l27.34,27.34 -27.34,27.34c-2.65,2.67 -2.65,6.97 0,9.64l29.67,29.67c2.67,2.65 6.97,2.65 9.64,0l27.34,-27.34 27.34,27.34c1.29,1.28 3.04,1.99 4.85,1.98 1.82,0.01 3.56,-0.7 4.85,-1.98l29.67,-29.67c2.65,-2.67 2.65,-6.97 0,-9.64l-27.55,-27.34ZM127.69,86.85c3.78,0 6.84,3.06 6.84,6.84s-3.06,6.84 -6.84,6.84 -6.84,-3.06 -6.84,-6.84 3.06,-6.84 6.84,-6.84ZM95.77,100.52l-24.81,-25.02 24.81,-24.81 25.09,24.75 -25.09,25.09ZM114.02,114.19c-3.78,0 -6.84,-3.06 -6.84,-6.84s3.06,-6.84 6.84,-6.84 6.84,3.06 6.84,6.84 -3.06,6.84 -6.84,6.84ZM127.69,127.86c-3.78,0 -6.84,-3.06 -6.84,-6.84s3.06,-6.84 6.84,-6.84 6.84,3.06 6.84,6.84 -3.06,6.84 -6.84,6.84ZM141.36,100.52c3.78,0 6.84,3.06 6.84,6.84s-3.06,6.84 -6.84,6.84 -6.84,-3.06 -6.84,-6.84 3.06,-6.84 6.84,-6.84ZM159.54,164.37l-24.81,-24.75 24.81,-24.81 24.75,24.75 -24.75,24.81Z" />
|
||||
</group>
|
||||
</vector>
|
||||
|
|
|
|||
|
|
@ -21,12 +21,16 @@
|
|||
|
||||
<!-- Manage Page -->
|
||||
<string name="page_manage">Manage</string>
|
||||
<string name="manage_loading">Loading</string>
|
||||
<string name="manage_no_apps">No patched apps yet</string>
|
||||
|
||||
<!-- New Patch Page -->
|
||||
<string name="page_new_patch">New Patch</string>
|
||||
<string name="patch_select_dir_title">Select storage directory</string>
|
||||
<string name="patch_select_dir_text">Select a directory to store the patched apks</string>
|
||||
<string name="patch_select_dir_error">Error when setting storage directory</string>
|
||||
<string name="patch_from_storage">Select apk(s) from storage</string>
|
||||
<string name="patch_from_applist">Select an installed app</string>
|
||||
<string name="patch_mode">Patch Mode</string>
|
||||
<string name="patch_local">Local</string>
|
||||
<string name="patch_local_desc">Patch an app without modules embedded.\nThe patched app need the manager running in background, and Xposed scope can be changed dynamically without re-patch.\nLocal patched apps can only run on the local device.</string>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
val verCode: Int by rootProject.extra
|
||||
val verName: String by rootProject.extra
|
||||
val androidSourceCompatibility: JavaVersion by rootProject.extra
|
||||
val androidTargetCompatibility: JavaVersion by rootProject.extra
|
||||
|
||||
|
|
@ -14,9 +16,9 @@ dependencies {
|
|||
implementation(projects.patch)
|
||||
}
|
||||
|
||||
tasks.jar {
|
||||
archiveBaseName.set("lspatch")
|
||||
destinationDirectory.set(file("${rootProject.projectDir}/out"))
|
||||
fun Jar.configure(variant: String) {
|
||||
archiveBaseName.set("jar-v$verName-$verCode-$variant")
|
||||
destinationDirectory.set(file("${rootProject.projectDir}/out/$variant"))
|
||||
manifest {
|
||||
attributes("Main-Class" to "org.lsposed.patch.LSPatch")
|
||||
}
|
||||
|
|
@ -31,21 +33,14 @@ tasks.jar {
|
|||
exclude("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA", "META-INF/*.MF", "META-INF/*.txt", "META-INF/versions/**")
|
||||
}
|
||||
|
||||
val jar = tasks.jar.get()
|
||||
|
||||
tasks.register("buildDebug") {
|
||||
jar.dependsOn(":appstub:copyDebug")
|
||||
jar.dependsOn(":patch-loader:copyDebug")
|
||||
dependsOn(tasks.build)
|
||||
tasks.register<Jar>("buildDebug") {
|
||||
dependsOn(":appstub:copyDebug")
|
||||
dependsOn(":patch-loader:copyDebug")
|
||||
configure("debug")
|
||||
}
|
||||
|
||||
tasks.register("buildRelease") {
|
||||
jar.dependsOn(":appstub:copyRelease")
|
||||
jar.dependsOn(":patch-loader:copyRelease")
|
||||
dependsOn(tasks.build)
|
||||
}
|
||||
|
||||
tasks["build"].doLast {
|
||||
println("Build to " + jar.archiveFile)
|
||||
println("Try \'java -jar " + jar.archiveFileName + "\' find more help")
|
||||
tasks.register<Jar>("buildRelease") {
|
||||
dependsOn(":appstub:copyRelease")
|
||||
dependsOn(":patch-loader:copyRelease")
|
||||
configure("release")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.wind.meditor.property.ModificationProperty;
|
|||
import com.wind.meditor.utils.NodeValue;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.lsposed.lspatch.share.LSPConfig;
|
||||
import org.lsposed.lspatch.share.PatchConfig;
|
||||
import org.lsposed.patch.util.ApkSignatureHelper;
|
||||
import org.lsposed.patch.util.JavaLogger;
|
||||
|
|
@ -38,8 +39,10 @@ import java.security.KeyStore;
|
|||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
|
|
@ -160,7 +163,11 @@ public class LSPatch {
|
|||
var outputDir = new File(outputPath);
|
||||
outputDir.mkdirs();
|
||||
|
||||
File outputFile = new File(outputDir, String.format("%s-lv%s-lspatched.apk", FilenameUtils.getBaseName(apkFileName), sigbypassLevel)).getAbsoluteFile();
|
||||
File outputFile = new File(outputDir, String.format(
|
||||
Locale.getDefault(), "%s-%d-lspatched.apk",
|
||||
FilenameUtils.getBaseName(apkFileName),
|
||||
LSPConfig.instance.VERSION_CODE)
|
||||
).getAbsoluteFile();
|
||||
|
||||
if (outputFile.exists() && !forceOverwrite)
|
||||
throw new PatchError(outputPath + " exists. Use --force to overwrite");
|
||||
|
|
@ -237,7 +244,10 @@ public class LSPatch {
|
|||
|
||||
logger.i("Patching apk...");
|
||||
// modify manifest
|
||||
try (var is = new ByteArrayInputStream(modifyManifestFile(manifestEntry.open()))) {
|
||||
var config = new PatchConfig(useManager, sigbypassLevel, null, appComponentFactory);
|
||||
var configBytes = new Gson().toJson(config).getBytes(StandardCharsets.UTF_8);
|
||||
var metadata = Base64.getEncoder().encodeToString(configBytes);
|
||||
try (var is = new ByteArrayInputStream(modifyManifestFile(manifestEntry.open(), metadata))) {
|
||||
dstZFile.add(ANDROID_MANIFEST_XML, is);
|
||||
} catch (Throwable e) {
|
||||
throw new PatchError("Error when modifying manifest", e);
|
||||
|
|
@ -273,9 +283,8 @@ public class LSPatch {
|
|||
}
|
||||
|
||||
// save lspatch config to asset..
|
||||
var config = new PatchConfig(useManager, sigbypassLevel, originalSignature, appComponentFactory);
|
||||
var configBytes = new Gson().toJson(config).getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
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) {
|
||||
|
|
@ -343,13 +352,14 @@ public class LSPatch {
|
|||
}
|
||||
}
|
||||
|
||||
private byte[] modifyManifestFile(InputStream is) throws IOException {
|
||||
private byte[] modifyManifestFile(InputStream is, String metadata) throws IOException {
|
||||
ModificationProperty property = new ModificationProperty();
|
||||
|
||||
if (overrideVersionCode)
|
||||
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.VERSION_CODE, 1));
|
||||
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.DEBUGGABLE, debuggableFlag));
|
||||
property.addApplicationAttribute(new AttributeItem("appComponentFactory", PROXY_APP_COMPONENT_FACTORY));
|
||||
property.addMetaData(new ModificationProperty.MetaData("lspatch", metadata));
|
||||
// TODO: replace query_all with queries -> manager
|
||||
property.addUsesPermission("android.permission.QUERY_ALL_PACKAGES");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
val apiCode: Int by rootProject.extra
|
||||
val verCode: Int by rootProject.extra
|
||||
val verName: String by rootProject.extra
|
||||
val coreVerCode: Int by rootProject.extra
|
||||
val coreVerName: String by rootProject.extra
|
||||
val androidSourceCompatibility: JavaVersion by rootProject.extra
|
||||
val androidTargetCompatibility: JavaVersion by rootProject.extra
|
||||
|
||||
|
|
@ -9,3 +14,20 @@ java {
|
|||
sourceCompatibility = androidSourceCompatibility
|
||||
targetCompatibility = androidTargetCompatibility
|
||||
}
|
||||
|
||||
val generateTask = task<Copy>("generateJava") {
|
||||
val template = mapOf(
|
||||
"apiCode" to apiCode,
|
||||
"verCode" to verCode,
|
||||
"verName" to verName,
|
||||
"coreVerCode" to coreVerCode,
|
||||
"coreVerName" to coreVerName
|
||||
)
|
||||
inputs.properties(template)
|
||||
from("src/template/java")
|
||||
into("$buildDir/generated/java")
|
||||
expand(template)
|
||||
}
|
||||
|
||||
sourceSets["main"].java.srcDir("$buildDir/generated/java")
|
||||
tasks["compileJava"].dependsOn(generateTask)
|
||||
|
|
|
|||
|
|
@ -6,11 +6,13 @@ public class PatchConfig {
|
|||
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) {
|
||||
this.useManager = useManager;
|
||||
this.sigBypassLevel = sigBypassLevel;
|
||||
this.originalSignature = originalSignature;
|
||||
this.appComponentFactory = appComponentFactory;
|
||||
this.lspConfig = LSPConfig.instance;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package org.lsposed.lspatch.share;
|
||||
|
||||
public class LSPConfig {
|
||||
|
||||
public static final LSPConfig instance = new LSPConfig();
|
||||
|
||||
public final int API_CODE = ${apiCode};
|
||||
public final int VERSION_CODE = ${verCode};
|
||||
public final String VERSION_NAME = "${verName}";
|
||||
public final int CORE_VERSION_CODE = ${coreVerCode};
|
||||
public final String CORE_VERSION_NAME = "${coreVerName}";
|
||||
|
||||
private LSPConfig() {
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue