Skip to content

Commit a17427d

Browse files
committed
Enhance installation and cleanup processes#29
1 parent 5180d73 commit a17427d

File tree

10 files changed

+347
-19
lines changed

10 files changed

+347
-19
lines changed

manager/src/main/AndroidManifest.xml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
android:name="android.permission.QUERY_ALL_PACKAGES"
77
tools:ignore="QueryAllPackagesPermission" />
88
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
9+
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
910

1011
<application
1112
android:name=".LSPApplication"
@@ -33,6 +34,14 @@
3334
</intent-filter>
3435
</activity>
3536

37+
<receiver
38+
android:name=".ui.util.InstallResultReceiver"
39+
android:exported="true">
40+
<intent-filter>
41+
<action android:name="${applicationId}.INSTALL_STATUS" />
42+
</intent-filter>
43+
</receiver>
44+
3645
<service
3746
android:name=".manager.ModuleService"
3847
android:exported="true" />
@@ -44,6 +53,16 @@
4453
android:exported="true"
4554
android:multiprocess="false"
4655
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
56+
57+
<provider
58+
android:name="androidx.core.content.FileProvider"
59+
android:authorities="${applicationId}.fileprovider"
60+
android:exported="false"
61+
android:grantUriPermissions="true">
62+
<meta-data
63+
android:name="android.support.FILE_PROVIDER_PATHS"
64+
android:resource="@xml/file_paths" />
65+
</provider>
4766
</application>
4867

4968
</manifest>

manager/src/main/java/org/lsposed/lspatch/LSPApplication.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class LSPApplication : Application() {
1818

1919
lateinit var prefs: SharedPreferences
2020
lateinit var tmpApkDir: File
21-
21+
var targetApkFiles: ArrayList<File>? = null
2222
val globalScope = CoroutineScope(Dispatchers.Default)
2323

2424
override fun onCreate() {

manager/src/main/java/org/lsposed/lspatch/Patcher.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import org.lsposed.lspatch.share.Constants
1010
import org.lsposed.lspatch.share.PatchConfig
1111
import org.lsposed.patch.LSPatch
1212
import org.lsposed.patch.util.Logger
13+
import java.io.File
1314
import java.io.IOException
1415
import java.util.Collections.addAll
1516

@@ -52,19 +53,25 @@ object Patcher {
5253
root.listFiles().forEach {
5354
if (it.name?.endsWith(Constants.PATCH_FILE_SUFFIX) == true) it.delete()
5455
}
56+
lspApp.targetApkFiles?.clear()
57+
val apkFileList = arrayListOf<File>()
5558
lspApp.tmpApkDir.walk()
5659
.filter { it.name.endsWith(Constants.PATCH_FILE_SUFFIX) }
5760
.forEach { apk ->
5861
val file = root.createFile("application/vnd.android.package-archive", apk.name)
5962
?: throw IOException("Failed to create output file")
6063
val output = lspApp.contentResolver.openOutputStream(file.uri)
6164
?: throw IOException("Failed to open output stream")
65+
val apkFile = File(lspApp.externalCacheDir, apk.name)
66+
apk.copyTo(apkFile, overwrite = true)
67+
apkFileList.add(apkFile)
6268
output.use {
6369
apk.inputStream().use { input ->
6470
input.copyTo(output)
6571
}
6672
}
6773
}
74+
lspApp.targetApkFiles = apkFileList
6875
logger.i("Patched files are saved to ${root.uri.lastPathSegment}")
6976
}
7077
}

manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchScreen.kt

Lines changed: 136 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package org.lsposed.lspatch.ui.page
22

3+
import android.annotation.SuppressLint
34
import android.content.ClipData
45
import android.content.ClipboardManager
56
import android.content.Context
7+
import android.content.Context.RECEIVER_NOT_EXPORTED
8+
import android.content.IntentFilter
69
import android.content.pm.PackageInstaller
710
import android.net.Uri
11+
import android.os.Build
812
import android.util.Log
913
import androidx.activity.compose.BackHandler
1014
import androidx.activity.compose.rememberLauncherForActivityResult
@@ -28,10 +32,14 @@ import androidx.compose.runtime.*
2832
import androidx.compose.ui.Alignment
2933
import androidx.compose.ui.Modifier
3034
import androidx.compose.ui.draw.clip
35+
import androidx.compose.ui.platform.LocalContext
3136
import androidx.compose.ui.res.stringResource
3237
import androidx.compose.ui.text.font.FontFamily
3338
import androidx.compose.ui.text.style.TextAlign
3439
import androidx.compose.ui.unit.dp
40+
import androidx.lifecycle.DefaultLifecycleObserver
41+
import androidx.lifecycle.LifecycleOwner
42+
import androidx.lifecycle.compose.LocalLifecycleOwner
3543
import androidx.lifecycle.viewmodel.compose.viewModel
3644
import com.ramcosta.composedestinations.annotation.Destination
3745
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
@@ -47,9 +55,14 @@ import org.lsposed.lspatch.ui.component.ShimmerAnimation
4755
import org.lsposed.lspatch.ui.component.settings.SettingsCheckBox
4856
import org.lsposed.lspatch.ui.component.settings.SettingsItem
4957
import org.lsposed.lspatch.ui.page.destinations.SelectAppsScreenDestination
58+
import org.lsposed.lspatch.ui.util.InstallResultReceiver
5059
import org.lsposed.lspatch.ui.util.LocalSnackbarHost
60+
import org.lsposed.lspatch.ui.util.checkIsApkFixedByLSP
61+
import org.lsposed.lspatch.ui.util.installApk
62+
import org.lsposed.lspatch.ui.util.installApks
5163
import org.lsposed.lspatch.ui.util.isScrolledToEnd
5264
import org.lsposed.lspatch.ui.util.lastItemIndex
65+
import org.lsposed.lspatch.ui.util.uninstallApkByPackageName
5366
import org.lsposed.lspatch.ui.viewmodel.NewPatchViewModel
5467
import org.lsposed.lspatch.ui.viewmodel.NewPatchViewModel.PatchState
5568
import org.lsposed.lspatch.ui.viewmodel.NewPatchViewModel.ViewAction
@@ -377,6 +390,7 @@ private fun PatchOptionsBody(modifier: Modifier, onAddEmbed: () -> Unit) {
377390
}
378391
}
379392

393+
@SuppressLint("UnusedBoxWithConstraintsScope")
380394
@Composable
381395
private fun DoPatchBody(modifier: Modifier, navigator: DestinationsNavigator) {
382396
val viewModel = viewModel<NewPatchViewModel>()
@@ -435,10 +449,9 @@ private fun DoPatchBody(modifier: Modifier, navigator: DestinationsNavigator) {
435449
val installSuccessfully = stringResource(R.string.patch_install_successfully)
436450
val installFailed = stringResource(R.string.patch_install_failed)
437451
val copyError = stringResource(R.string.copy_error)
438-
var installing by remember { mutableStateOf(false) }
439-
if (installing) InstallDialog(viewModel.patchApp) { status, message ->
452+
var installation by remember { mutableStateOf<NewPatchViewModel.InstallMethod?>(null) }
453+
val onFinish: (Int, String?) -> Unit = { status, message ->
440454
scope.launch {
441-
installing = false
442455
if (status == PackageInstaller.STATUS_SUCCESS) {
443456
lspApp.globalScope.launch { snackbarHost.showSnackbar(installSuccessfully) }
444457
navigator.navigateUp()
@@ -451,6 +464,11 @@ private fun DoPatchBody(modifier: Modifier, navigator: DestinationsNavigator) {
451464
}
452465
}
453466
}
467+
when (installation) {
468+
NewPatchViewModel.InstallMethod.SYSTEM -> InstallDialog2(viewModel.patchApp, onFinish)
469+
NewPatchViewModel.InstallMethod.SHIZUKU -> InstallDialog(viewModel.patchApp, onFinish)
470+
null -> {}
471+
}
454472
Row(Modifier.padding(top = 12.dp)) {
455473
Button(
456474
modifier = Modifier.weight(1f),
@@ -461,13 +479,8 @@ private fun DoPatchBody(modifier: Modifier, navigator: DestinationsNavigator) {
461479
Button(
462480
modifier = Modifier.weight(1f),
463481
onClick = {
464-
if (!ShizukuApi.isPermissionGranted) {
465-
scope.launch {
466-
snackbarHost.showSnackbar(shizukuUnavailable)
467-
}
468-
} else {
469-
installing = true
470-
}
482+
installation = if (!ShizukuApi.isPermissionGranted) NewPatchViewModel.InstallMethod.SYSTEM else NewPatchViewModel.InstallMethod.SHIZUKU
483+
Log.d(TAG, "Installation method: $installation")
471484
},
472485
content = { Text(stringResource(R.string.install)) }
473486
)
@@ -572,3 +585,116 @@ private fun InstallDialog(patchApp: AppInfo, onFinish: (Int, String?) -> Unit) {
572585
)
573586
}
574587
}
588+
589+
@Composable
590+
private fun InstallDialog2(patchApp: AppInfo, onFinish: (Int, String?) -> Unit) {
591+
val scope = rememberCoroutineScope()
592+
var uninstallFirst by remember {
593+
mutableStateOf(
594+
checkIsApkFixedByLSP(
595+
lspApp,
596+
patchApp.app.packageName
597+
)
598+
)
599+
}
600+
val lifecycleOwner = LocalLifecycleOwner.current
601+
val context = LocalContext.current
602+
val splitInstallReceiver by lazy { InstallResultReceiver() }
603+
fun doInstall() {
604+
Log.i(TAG, "Installing app ${patchApp.app.packageName}")
605+
val apkFiles = lspApp.targetApkFiles
606+
if (apkFiles.isNullOrEmpty()){
607+
onFinish(PackageInstaller.STATUS_FAILURE, "No target APK files found for installation")
608+
return
609+
}
610+
if (apkFiles.size > 1) {
611+
scope.launch {
612+
val success = installApks(lspApp, apkFiles)
613+
if (success) {
614+
onFinish(
615+
PackageInstaller.STATUS_SUCCESS,
616+
"Split APKs installed successfully"
617+
)
618+
} else {
619+
onFinish(
620+
PackageInstaller.STATUS_FAILURE,
621+
"Failed to install split APKs"
622+
)
623+
}
624+
}
625+
} else {
626+
installApk(lspApp, apkFiles.first())
627+
}
628+
}
629+
630+
DisposableEffect(lifecycleOwner) {
631+
val observer = object : DefaultLifecycleObserver {
632+
@SuppressLint("UnspecifiedRegisterReceiverFlag")
633+
override fun onCreate(owner: LifecycleOwner) {
634+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
635+
context.registerReceiver(splitInstallReceiver, IntentFilter(InstallResultReceiver.ACTION_INSTALL_STATUS), RECEIVER_NOT_EXPORTED)
636+
} else {
637+
context.registerReceiver(splitInstallReceiver, IntentFilter(InstallResultReceiver.ACTION_INSTALL_STATUS))
638+
}
639+
}
640+
641+
override fun onDestroy(owner: LifecycleOwner) {
642+
context.unregisterReceiver(splitInstallReceiver)
643+
}
644+
645+
override fun onResume(owner: LifecycleOwner) {
646+
if (!uninstallFirst) {
647+
Log.d(TAG,"Starting installation without uninstalling first")
648+
onFinish(LSPPackageManager.STATUS_USER_CANCELLED, "User cancelled")
649+
doInstall()
650+
}
651+
}
652+
}
653+
654+
lifecycleOwner.lifecycle.addObserver(observer)
655+
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
656+
}
657+
658+
if (uninstallFirst) {
659+
AlertDialog(
660+
onDismissRequest = {
661+
onFinish(
662+
LSPPackageManager.STATUS_USER_CANCELLED,
663+
"User cancelled"
664+
)
665+
},
666+
confirmButton = {
667+
TextButton(
668+
onClick = {
669+
onFinish(LSPPackageManager.STATUS_USER_CANCELLED, "Reset")
670+
scope.launch {
671+
Log.i(TAG, "Uninstalling app ${patchApp.app.packageName}")
672+
uninstallApkByPackageName(lspApp, patchApp.app.packageName)
673+
uninstallFirst = false
674+
}
675+
},
676+
content = { Text(stringResource(android.R.string.ok)) }
677+
)
678+
},
679+
dismissButton = {
680+
TextButton(
681+
onClick = {
682+
onFinish(
683+
LSPPackageManager.STATUS_USER_CANCELLED,
684+
"User cancelled"
685+
)
686+
},
687+
content = { Text(stringResource(android.R.string.cancel)) }
688+
)
689+
},
690+
title = {
691+
Text(
692+
modifier = Modifier.fillMaxWidth(),
693+
text = stringResource(R.string.uninstall),
694+
textAlign = TextAlign.Center
695+
)
696+
},
697+
text = { Text(stringResource(R.string.patch_uninstall_text)) }
698+
)
699+
}
700+
}

manager/src/main/java/org/lsposed/lspatch/ui/page/manage/AppManagePage.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,11 +192,7 @@ fun AppManageBody(
192192
onClick = {
193193
expanded = false
194194
scope.launch {
195-
if (!ShizukuApi.isPermissionGranted) {
196-
snackbarHost.showSnackbar(shizukuUnavailable)
197-
} else {
198-
viewModel.dispatch(AppManageViewModel.ViewAction.UpdateLoader(it.first, it.second))
199-
}
195+
viewModel.dispatch(AppManageViewModel.ViewAction.UpdateLoader(it.first, it.second))
200196
}
201197
}
202198
)

0 commit comments

Comments
 (0)