diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml
index ca6ce99aa..7a362a8a8 100644
--- a/client/android/AndroidManifest.xml
+++ b/client/android/AndroidManifest.xml
@@ -15,8 +15,9 @@
-
-
+
+
+
diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt
index 44f03e882..92223453b 100644
--- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt
+++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt
@@ -321,13 +321,9 @@ class AmneziaActivity : QtActivity() {
// saving file
private fun alterDocument(uri: Uri) {
try {
- applicationContext.contentResolver.openFileDescriptor(uri, "w")?.use { fd ->
- FileOutputStream(fd.fileDescriptor).use { fos ->
- fos.write(tmpFileContentToSave.toByteArray())
- }
+ contentResolver.openOutputStream(uri)?.use { os ->
+ os.bufferedWriter().use { it.write(tmpFileContentToSave) }
}
- } catch (e: FileNotFoundException) {
- e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
}
@@ -363,16 +359,17 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused")
fun saveFile(fileName: String, data: String) {
Log.v(TAG, "Save file $fileName")
- // todo: refactor
- tmpFileContentToSave = data
+ mainScope.launch {
+ tmpFileContentToSave = data
- val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
- addCategory(Intent.CATEGORY_OPENABLE)
- type = "text/*"
- putExtra(Intent.EXTRA_TITLE, fileName)
+ Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ type = "text/*"
+ putExtra(Intent.EXTRA_TITLE, fileName)
+ }.also {
+ startActivityForResult(it, CREATE_FILE_ACTION_CODE)
+ }
}
-
- startActivityForResult(intent, CREATE_FILE_ACTION_CODE)
}
@Suppress("unused")
diff --git a/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt b/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt
index 2eaad7319..4910d851f 100644
--- a/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt
+++ b/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt
@@ -1,17 +1,23 @@
package org.amnezia.vpn
+import android.Manifest
import android.content.Intent
import android.content.Intent.ACTION_SEND
import android.content.Intent.ACTION_VIEW
import android.content.Intent.CATEGORY_DEFAULT
import android.content.Intent.EXTRA_TEXT
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.os.Bundle
+import android.os.Process
+import android.widget.Toast
import androidx.activity.ComponentActivity
+import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
import java.io.BufferedReader
+import java.io.IOException
import org.amnezia.vpn.util.Log
private const val TAG = "ImportConfigActivity"
@@ -38,8 +44,16 @@ class ImportConfigActivity : ComponentActivity() {
ACTION_SEND -> {
Log.v(TAG, "Process SEND action, type: ${intent.type}")
when (intent.type) {
- "application/octet-stream" ->
- processStream(intent)
+ "application/octet-stream" -> {
+ intent.getUriCompat()?.let { uri ->
+ checkPermissions(
+ uri,
+ onSuccess = ::processUri,
+ onFail = ::finish
+ )
+ }
+ return
+ }
"text/plain" -> {
intent.getStringExtra(EXTRA_TEXT)?.let(::startMainActivity)
@@ -55,20 +69,49 @@ class ImportConfigActivity : ComponentActivity() {
finish()
}
- private fun processStream(intent: Intent) {
- getUriCompat(intent)?.let { uri ->
- contentResolver.openInputStream(uri)?.use {
- it.bufferedReader().use(BufferedReader::readText).let(::startMainActivity)
- }
+ private fun checkPermissions(uri: Uri, onSuccess: (Uri) -> Unit, onFail: () -> Unit) {
+ if (checkUriReadPermission(uri) == PackageManager.PERMISSION_GRANTED) {
+ onSuccess(uri)
+ } else {
+ val requestPermissionLauncher =
+ registerForActivityResult(RequestPermission()) { isGranted ->
+ if (isGranted) onSuccess(uri)
+ else {
+ Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show()
+ onFail()
+ }
+ }
+ requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
}
}
- private fun getUriCompat(intent: Intent): Uri? =
+ private fun checkUriReadPermission(uri: Uri) = checkUriPermission(
+ uri,
+ Manifest.permission.READ_EXTERNAL_STORAGE,
+ null,
+ Process.myPid(),
+ Process.myUid(),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION
+ )
+
+ private fun processUri(uri: Uri) {
+ try {
+ contentResolver.openInputStream(uri)?.use {
+ it.bufferedReader().use(BufferedReader::readText).let(::startMainActivity)
+ }
+ } catch (e: IOException) {
+ e.printStackTrace()
+ } finally {
+ finish()
+ }
+ }
+
+ private fun Intent.getUriCompat(): Uri? =
if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
- intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)
+ getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)
} else {
@Suppress("DEPRECATION")
- intent.getParcelableExtra(Intent.EXTRA_STREAM)
+ getParcelableExtra(Intent.EXTRA_STREAM)
}
private fun startMainActivity(config: String) {
@@ -83,6 +126,5 @@ class ImportConfigActivity : ComponentActivity() {
startActivity(it)
}
}
- finish()
}
}