From 3b5fa88d17aad28f70e39e278ff54ac0c76dbf5f Mon Sep 17 00:00:00 2001
From: Nullptr <52071314+Dr-TSNG@users.noreply.github.com>
Date: Fri, 22 Apr 2022 21:22:35 +0800
Subject: [PATCH] Fix saving apks
---
gradle.properties | 2 +-
manager/build.gradle.kts | 5 +-
manager/src/main/AndroidManifest.xml | 7 +-
.../java/org/lsposed/lspatch/Constants.kt | 6 ++
.../main/java/org/lsposed/lspatch/Patcher.kt | 41 ++++-----
.../org/lsposed/lspatch/ui/page/ManagePage.kt | 87 +++++++++++++++++--
.../lsposed/lspatch/ui/page/NewPatchPage.kt | 28 +-----
manager/src/main/res/values/strings.xml | 5 +-
8 files changed, 109 insertions(+), 72 deletions(-)
create mode 100644 manager/src/main/java/org/lsposed/lspatch/Constants.kt
diff --git a/gradle.properties b/gradle.properties
index c43f29b..3e202fb 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -5,4 +5,4 @@ android.nonTransitiveRClass=true
android.enableR8.fullMode=true
android.useAndroidX=true
-agpVersion=7.1.2
+agpVersion=7.1.3
diff --git a/manager/build.gradle.kts b/manager/build.gradle.kts
index e99756a..5cf4912 100644
--- a/manager/build.gradle.kts
+++ b/manager/build.gradle.kts
@@ -74,12 +74,11 @@ dependencies {
implementation("androidx.compose.runtime:runtime-livedata:1.1.1")
implementation("androidx.compose.ui:ui:1.1.1")
implementation("androidx.compose.ui:ui-tooling:1.1.1")
- implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-alpha05")
- implementation("androidx.navigation:navigation-compose:2.5.0-alpha03")
+ implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-beta01")
+ implementation("androidx.navigation:navigation-compose:2.5.0-beta01")
implementation("androidx.preference:preference:1.2.0")
implementation("com.google.accompanist:accompanist-drawablepainter:0.24.5-alpha")
implementation("com.google.accompanist:accompanist-navigation-animation:0.24.5-alpha")
- implementation("com.google.accompanist:accompanist-permissions:0.24.5-alpha")
implementation("com.google.accompanist:accompanist-swiperefresh:0.24.5-alpha")
implementation("com.google.android.material:material:1.5.0")
implementation("dev.rikka.shizuku:api:12.1.0")
diff --git a/manager/src/main/AndroidManifest.xml b/manager/src/main/AndroidManifest.xml
index 7953c1f..a980f8d 100644
--- a/manager/src/main/AndroidManifest.xml
+++ b/manager/src/main/AndroidManifest.xml
@@ -7,10 +7,6 @@
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
-
-
+ android:exported="true"
+ tools:ignore="ExportedContentProvider" />
it.inputStream().use { input ->
- externalStorageDir.resolve(it.name).outputStream().use { output ->
- input.copyTo(output)
- }
- }
- } else {
- val contentDetails = ContentValues().apply {
- put(MediaStore.Downloads.DISPLAY_NAME, it.name)
- put(MediaStore.Downloads.RELATIVE_PATH, download)
- }
- val uri = context.contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentDetails)
- ?: throw IllegalStateException("Failed to save files to Download")
- it.inputStream().use { input ->
- context.contentResolver.openOutputStream(uri)!!.use { output ->
- input.copyTo(output)
- }
+ input.copyTo(output)
}
}
}
- logger.i("Patched files are saved to $download")
+ logger.i("Patched files are saved to ${root.uri.lastPathSegment}")
}
}
}
diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/ManagePage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/ManagePage.kt
index a90241f..f045b9a 100644
--- a/manager/src/main/java/org/lsposed/lspatch/ui/page/ManagePage.kt
+++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/ManagePage.kt
@@ -1,22 +1,33 @@
package org.lsposed.lspatch.ui.page
+import android.app.Activity
+import android.content.Intent
+import android.util.Log
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.*
-import androidx.compose.runtime.Composable
+import androidx.compose.runtime.*
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
+import androidx.core.net.toUri
+import kotlinx.coroutines.launch
+import org.lsposed.lspatch.Constants
+import org.lsposed.lspatch.LSPApplication
import org.lsposed.lspatch.R
+import org.lsposed.lspatch.TAG
import org.lsposed.lspatch.ui.util.LocalNavController
+import java.io.IOException
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ManagePage() {
- val navController = LocalNavController.current
+ val snackbarHostState = remember { SnackbarHostState() }
Scaffold(
topBar = { TopBar() },
- floatingActionButton = {
- Fab { navController.navigate(PageList.NewPatch.name) }
- }
+ floatingActionButton = { Fab(snackbarHostState) },
+ snackbarHost = { SnackbarHost(snackbarHostState) }
) { innerPadding ->
}
@@ -30,8 +41,68 @@ private fun TopBar() {
}
@Composable
-private fun Fab(onClick: () -> Unit) {
- FloatingActionButton(onClick = onClick) {
- Icon(Icons.Filled.Add, stringResource(R.string.add))
+private fun Fab(snackbarHostState: SnackbarHostState) {
+ val context = LocalContext.current
+ val navController = LocalNavController.current
+ val scope = rememberCoroutineScope()
+ var shouldSelectDirectory by remember { mutableStateOf(false) }
+
+ val errorText = stringResource(R.string.patch_select_dir_error)
+ val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ try {
+ if (it.resultCode == Activity.RESULT_CANCELED) return@rememberLauncherForActivityResult
+ val uri = it.data?.data ?: throw IOException("No data")
+ val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ context.contentResolver.takePersistableUriPermission(uri, takeFlags)
+ LSPApplication.prefs.edit().putString(Constants.PREFS_STORAGE_DIRECTORY, uri.toString()).apply()
+ Log.i(TAG, "Storage directory: ${uri.path}")
+ navController.navigate(PageList.NewPatch.name)
+ } catch (e: Exception) {
+ Log.e(TAG, "Error when requesting saving directory", e)
+ scope.launch { snackbarHostState.showSnackbar(errorText) }
+ }
}
+
+ if (shouldSelectDirectory) {
+ AlertDialog(
+ onDismissRequest = { shouldSelectDirectory = false },
+ confirmButton = {
+ TextButton(
+ content = { Text(stringResource(android.R.string.ok)) },
+ onClick = {
+ launcher.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE))
+ shouldSelectDirectory = false
+ }
+ )
+ },
+ dismissButton = {
+ TextButton(
+ content = { Text(stringResource(android.R.string.cancel)) },
+ onClick = { shouldSelectDirectory = false }
+ )
+ },
+ title = { Text(stringResource(R.string.patch_select_dir_title)) },
+ text = { Text(stringResource(R.string.patch_select_dir_text)) }
+ )
+ }
+
+ FloatingActionButton(
+ content = { Icon(Icons.Filled.Add, stringResource(R.string.add)) },
+ onClick = {
+ val uri = LSPApplication.prefs.getString(Constants.PREFS_STORAGE_DIRECTORY, null)?.toUri()
+ if (uri == null) {
+ shouldSelectDirectory = true
+ } else {
+ try {
+ val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ context.contentResolver.takePersistableUriPermission(uri, takeFlags)
+ navController.navigate(PageList.NewPatch.name)
+ } catch (e: SecurityException) {
+ Log.e(TAG, "Failed to take persistable permission for saved uri", e)
+ LSPApplication.prefs.edit().putString(Constants.PREFS_STORAGE_DIRECTORY, null).apply()
+ shouldSelectDirectory = true
+ }
+ }
+ }
+ )
}
diff --git a/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt b/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt
index 47ce0b4..5b125c2 100644
--- a/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt
+++ b/manager/src/main/java/org/lsposed/lspatch/ui/page/NewPatchPage.kt
@@ -3,7 +3,6 @@ package org.lsposed.lspatch.ui.page
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
-import android.os.Build
import android.util.Log
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.Spring
@@ -31,9 +30,6 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavBackStackEntry
-import com.google.accompanist.permissions.ExperimentalPermissionsApi
-import com.google.accompanist.permissions.PermissionStatus
-import com.google.accompanist.permissions.rememberPermissionState
import org.lsposed.lspatch.Patcher
import org.lsposed.lspatch.R
import org.lsposed.lspatch.TAG
@@ -54,7 +50,7 @@ private enum class PatchState {
SELECTING, CONFIGURING, SUBMITTING, PATCHING, FINISHED, ERROR
}
-@OptIn(ExperimentalPermissionsApi::class, ExperimentalMaterial3Api::class)
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NewPatchPage(entry: NavBackStackEntry) {
val navController = LocalNavController.current
@@ -72,28 +68,6 @@ fun NewPatchPage(entry: NavBackStackEntry) {
}
}
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
- val filePermissionState = rememberPermissionState(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
- if (filePermissionState.status is PermissionStatus.Denied) {
- AlertDialog(
- onDismissRequest = {},
- confirmButton = {
- TextButton(onClick = { filePermissionState.launchPermissionRequest() }) {
- Text(stringResource(android.R.string.ok))
- }
- },
- dismissButton = {
- TextButton(onClick = { navController.popBackStack() }) {
- Text(stringResource(android.R.string.cancel))
- }
- },
- title = { Text(stringResource(R.string.patch_permission_title)) },
- text = { Text(stringResource(R.string.patch_permission_text)) }
- )
- return
- }
- }
-
Log.d(TAG, "NewPatchPage: $patchState")
if (patchState == PatchState.SELECTING) {
LaunchedEffect(entry) {
diff --git a/manager/src/main/res/values/strings.xml b/manager/src/main/res/values/strings.xml
index 43872c3..556828b 100644
--- a/manager/src/main/res/values/strings.xml
+++ b/manager/src/main/res/values/strings.xml
@@ -24,8 +24,9 @@
New Patch
- Permission Application
- Please grant Write External Storage permission to allow saving patched apks to Download directory
+ Select storage directory
+ Select a directory to store the patched apks
+ Error when setting storage directory
Patch Mode
Local
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.