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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions manager/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>

<application
android:name=".LSPApplication"
Expand Down Expand Up @@ -33,6 +34,14 @@
</intent-filter>
</activity>

<receiver
android:name=".ui.util.InstallResultReceiver"
android:exported="true">
<intent-filter>
<action android:name="${applicationId}.INSTALL_STATUS" />
</intent-filter>
</receiver>

<service
android:name=".manager.ModuleService"
android:exported="true" />
Expand All @@ -44,6 +53,16 @@
android:exported="true"
android:multiprocess="false"
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class LSPApplication : Application() {

lateinit var prefs: SharedPreferences
lateinit var tmpApkDir: File

var targetApkFiles: ArrayList<File>? = null
val globalScope = CoroutineScope(Dispatchers.Default)

override fun onCreate() {
Expand Down
7 changes: 7 additions & 0 deletions manager/src/main/java/org/lsposed/lspatch/Patcher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.lsposed.lspatch.share.Constants
import org.lsposed.lspatch.share.PatchConfig
import org.lsposed.patch.LSPatch
import org.lsposed.patch.util.Logger
import java.io.File
import java.io.IOException
import java.util.Collections.addAll

Expand Down Expand Up @@ -52,19 +53,25 @@ object Patcher {
root.listFiles().forEach {
if (it.name?.endsWith(Constants.PATCH_FILE_SUFFIX) == true) it.delete()
}
lspApp.targetApkFiles?.clear()
val apkFileList = arrayListOf<File>()
lspApp.tmpApkDir.walk()
.filter { it.name.endsWith(Constants.PATCH_FILE_SUFFIX) }
.forEach { apk ->
val file = root.createFile("application/vnd.android.package-archive", apk.name)
?: throw IOException("Failed to create output file")
val output = lspApp.contentResolver.openOutputStream(file.uri)
?: throw IOException("Failed to open output stream")
val apkFile = File(lspApp.externalCacheDir, apk.name)
apk.copyTo(apkFile, overwrite = true)
apkFileList.add(apkFile)
output.use {
apk.inputStream().use { input ->
input.copyTo(output)
}
}
}
lspApp.targetApkFiles = apkFileList
logger.i("Patched files are saved to ${root.uri.lastPathSegment}")
}
}
Expand Down
146 changes: 136 additions & 10 deletions manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchScreen.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package org.lsposed.lspatch.ui.page

import android.annotation.SuppressLint
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Context.RECEIVER_NOT_EXPORTED
import android.content.IntentFilter
import android.content.pm.PackageInstaller
import android.net.Uri
import android.os.Build
import android.util.Log
import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
Expand All @@ -28,10 +32,14 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.viewmodel.compose.viewModel
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
Expand All @@ -47,9 +55,14 @@ 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.page.destinations.SelectAppsScreenDestination
import org.lsposed.lspatch.ui.util.InstallResultReceiver
import org.lsposed.lspatch.ui.util.LocalSnackbarHost
import org.lsposed.lspatch.ui.util.checkIsApkFixedByLSP
import org.lsposed.lspatch.ui.util.installApk
import org.lsposed.lspatch.ui.util.installApks
import org.lsposed.lspatch.ui.util.isScrolledToEnd
import org.lsposed.lspatch.ui.util.lastItemIndex
import org.lsposed.lspatch.ui.util.uninstallApkByPackageName
import org.lsposed.lspatch.ui.viewmodel.NewPatchViewModel
import org.lsposed.lspatch.ui.viewmodel.NewPatchViewModel.PatchState
import org.lsposed.lspatch.ui.viewmodel.NewPatchViewModel.ViewAction
Expand Down Expand Up @@ -377,6 +390,7 @@ private fun PatchOptionsBody(modifier: Modifier, onAddEmbed: () -> Unit) {
}
}

@SuppressLint("UnusedBoxWithConstraintsScope")
@Composable
private fun DoPatchBody(modifier: Modifier, navigator: DestinationsNavigator) {
val viewModel = viewModel<NewPatchViewModel>()
Expand Down Expand Up @@ -435,10 +449,9 @@ private fun DoPatchBody(modifier: Modifier, navigator: DestinationsNavigator) {
val installSuccessfully = stringResource(R.string.patch_install_successfully)
val installFailed = stringResource(R.string.patch_install_failed)
val copyError = stringResource(R.string.copy_error)
var installing by remember { mutableStateOf(false) }
if (installing) InstallDialog(viewModel.patchApp) { status, message ->
var installation by remember { mutableStateOf<NewPatchViewModel.InstallMethod?>(null) }
val onFinish: (Int, String?) -> Unit = { status, message ->
scope.launch {
installing = false
if (status == PackageInstaller.STATUS_SUCCESS) {
lspApp.globalScope.launch { snackbarHost.showSnackbar(installSuccessfully) }
navigator.navigateUp()
Expand All @@ -451,6 +464,11 @@ private fun DoPatchBody(modifier: Modifier, navigator: DestinationsNavigator) {
}
}
}
when (installation) {
NewPatchViewModel.InstallMethod.SYSTEM -> InstallDialog2(viewModel.patchApp, onFinish)
NewPatchViewModel.InstallMethod.SHIZUKU -> InstallDialog(viewModel.patchApp, onFinish)
null -> {}
}
Row(Modifier.padding(top = 12.dp)) {
Button(
modifier = Modifier.weight(1f),
Expand All @@ -461,13 +479,8 @@ private fun DoPatchBody(modifier: Modifier, navigator: DestinationsNavigator) {
Button(
modifier = Modifier.weight(1f),
onClick = {
if (!ShizukuApi.isPermissionGranted) {
scope.launch {
snackbarHost.showSnackbar(shizukuUnavailable)
}
} else {
installing = true
}
installation = if (!ShizukuApi.isPermissionGranted) NewPatchViewModel.InstallMethod.SYSTEM else NewPatchViewModel.InstallMethod.SHIZUKU
Log.d(TAG, "Installation method: $installation")
},
content = { Text(stringResource(R.string.install)) }
)
Expand Down Expand Up @@ -572,3 +585,116 @@ private fun InstallDialog(patchApp: AppInfo, onFinish: (Int, String?) -> Unit) {
)
}
}

@Composable
private fun InstallDialog2(patchApp: AppInfo, onFinish: (Int, String?) -> Unit) {
val scope = rememberCoroutineScope()
var uninstallFirst by remember {
mutableStateOf(
checkIsApkFixedByLSP(
lspApp,
patchApp.app.packageName
)
)
}
val lifecycleOwner = LocalLifecycleOwner.current
val context = LocalContext.current
val splitInstallReceiver by lazy { InstallResultReceiver() }
fun doInstall() {
Log.i(TAG, "Installing app ${patchApp.app.packageName}")
val apkFiles = lspApp.targetApkFiles
if (apkFiles.isNullOrEmpty()){
onFinish(PackageInstaller.STATUS_FAILURE, "No target APK files found for installation")
return
}
if (apkFiles.size > 1) {
scope.launch {
val success = installApks(lspApp, apkFiles)
if (success) {
onFinish(
PackageInstaller.STATUS_SUCCESS,
"Split APKs installed successfully"
)
} else {
onFinish(
PackageInstaller.STATUS_FAILURE,
"Failed to install split APKs"
)
}
}
} else {
installApk(lspApp, apkFiles.first())
}
}

DisposableEffect(lifecycleOwner) {
val observer = object : DefaultLifecycleObserver {
@SuppressLint("UnspecifiedRegisterReceiverFlag")
override fun onCreate(owner: LifecycleOwner) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(splitInstallReceiver, IntentFilter(InstallResultReceiver.ACTION_INSTALL_STATUS), RECEIVER_NOT_EXPORTED)
} else {
context.registerReceiver(splitInstallReceiver, IntentFilter(InstallResultReceiver.ACTION_INSTALL_STATUS))
}
}

override fun onDestroy(owner: LifecycleOwner) {
context.unregisterReceiver(splitInstallReceiver)
}

override fun onResume(owner: LifecycleOwner) {
if (!uninstallFirst) {
Log.d(TAG,"Starting installation without uninstalling first")
onFinish(LSPPackageManager.STATUS_USER_CANCELLED, "User cancelled")
doInstall()
}
}
}

lifecycleOwner.lifecycle.addObserver(observer)
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}

if (uninstallFirst) {
AlertDialog(
onDismissRequest = {
onFinish(
LSPPackageManager.STATUS_USER_CANCELLED,
"User cancelled"
)
},
confirmButton = {
TextButton(
onClick = {
onFinish(LSPPackageManager.STATUS_USER_CANCELLED, "Reset")
scope.launch {
Log.i(TAG, "Uninstalling app ${patchApp.app.packageName}")
uninstallApkByPackageName(lspApp, patchApp.app.packageName)
uninstallFirst = false
}
},
content = { Text(stringResource(android.R.string.ok)) }
)
},
dismissButton = {
TextButton(
onClick = {
onFinish(
LSPPackageManager.STATUS_USER_CANCELLED,
"User cancelled"
)
},
content = { Text(stringResource(android.R.string.cancel)) }
)
},
title = {
Text(
modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.uninstall),
textAlign = TextAlign.Center
)
},
text = { Text(stringResource(R.string.patch_uninstall_text)) }
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -192,11 +192,7 @@ fun AppManageBody(
onClick = {
expanded = false
scope.launch {
if (!ShizukuApi.isPermissionGranted) {
snackbarHost.showSnackbar(shizukuUnavailable)
} else {
viewModel.dispatch(AppManageViewModel.ViewAction.UpdateLoader(it.first, it.second))
}
viewModel.dispatch(AppManageViewModel.ViewAction.UpdateLoader(it.first, it.second))
}
}
)
Expand Down
Loading