aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2020-09-16 17:56:07 +0200
committerJason A. Donenfeld <Jason@zx2c4.com>2020-09-17 14:50:37 +0200
commita9ec8285062636fc7be68fee123bb5899e639fdd (patch)
treef11be67e983779e74eb0f5431b2bcda0e5c0d480
parentLogViewerActivity: simplify scoping (diff)
downloadwireguard-android-a9ec8285062636fc7be68fee123bb5899e639fdd.tar.xz
wireguard-android-a9ec8285062636fc7be68fee123bb5899e639fdd.zip
DownloadsFileSaver: encapsulate permission checks
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
-rw-r--r--build.gradle3
-rw-r--r--ui/build.gradle1
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/LogViewerActivity.kt7
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt38
-rw-r--r--ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt23
-rw-r--r--ui/src/main/java/com/wireguard/android/util/DownloadsFileSaver.kt89
6 files changed, 70 insertions, 91 deletions
diff --git a/build.gradle b/build.gradle
index 893a8976..9810ab76 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,5 +1,6 @@
buildscript {
ext {
+ activityVersion = '1.2.0-alpha08'
agpVersion = '4.0.1'
annotationsVersion = '1.1.0'
appcompatVersion = '1.2.0'
@@ -11,7 +12,7 @@ buildscript {
coreKtxVersion = '1.3.1'
coroutinesVersion = '1.3.9'
desugarVersion = '1.0.10'
- fragmentVersion = '1.2.5'
+ fragmentVersion = '1.3.0-alpha08'
jsr305Version = '3.0.2'
junitVersion = '4.13'
kotlinVersion = '1.4.10'
diff --git a/ui/build.gradle b/ui/build.gradle
index 26739f10..b764143d 100644
--- a/ui/build.gradle
+++ b/ui/build.gradle
@@ -57,6 +57,7 @@ android {
dependencies {
implementation project(":tunnel")
+ implementation "androidx.activity:activity-ktx:$activityVersion"
implementation "androidx.annotation:annotation:$annotationsVersion"
implementation "androidx.appcompat:appcompat:$appcompatVersion"
implementation "androidx.constraintlayout:constraintlayout:$constraintLayoutVersion"
diff --git a/ui/src/main/java/com/wireguard/android/activity/LogViewerActivity.kt b/ui/src/main/java/com/wireguard/android/activity/LogViewerActivity.kt
index 456ad6ce..19f67834 100644
--- a/ui/src/main/java/com/wireguard/android/activity/LogViewerActivity.kt
+++ b/ui/src/main/java/com/wireguard/android/activity/LogViewerActivity.kt
@@ -60,7 +60,6 @@ import java.util.regex.Matcher
import java.util.regex.Pattern
class LogViewerActivity : AppCompatActivity() {
-
private lateinit var binding: LogViewerActivityBinding
private lateinit var logAdapter: LogEntryAdapter
private var logLines = arrayListOf<LogLine>()
@@ -161,15 +160,15 @@ class LogViewerActivity : AppCompatActivity() {
withContext(Dispatchers.IO) {
try {
outputFile = DownloadsFileSaver.save(this@LogViewerActivity, "wireguard-log.txt", "text/plain", true)
- outputFile?.outputStream.use {
- it?.write(rawLogLines.toString().toByteArray(Charsets.UTF_8))
- }
+ outputFile?.outputStream?.write(rawLogLines.toString().toByteArray(Charsets.UTF_8))
} catch (e: Throwable) {
outputFile?.delete()
exception = e
}
}
saveButton?.isEnabled = true
+ if (outputFile == null)
+ return
Snackbar.make(findViewById(android.R.id.content),
if (exception == null) getString(R.string.log_export_success, outputFile?.fileName)
else getString(R.string.log_export_error, ErrorMessages[exception]),
diff --git a/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt b/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt
index 8d4c181c..feac43ca 100644
--- a/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt
+++ b/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt
@@ -5,13 +5,9 @@
package com.wireguard.android.activity
import android.content.Intent
-import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
-import android.util.SparseArray
import android.view.MenuItem
-import androidx.core.app.ActivityCompat
-import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
@@ -23,35 +19,11 @@ import com.wireguard.android.util.ModuleLoader
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import java.util.ArrayList
-import java.util.Arrays
/**
* Interface for changing application-global persistent settings.
*/
class SettingsActivity : ThemeChangeAwareActivity() {
- private val permissionRequestCallbacks = SparseArray<(permissions: Array<String>, granted: IntArray) -> Unit>()
- private var permissionRequestCounter = 0
-
- fun ensurePermissions(permissions: Array<String>, cb: (permissions: Array<String>, granted: IntArray) -> Unit) {
- val needPermissions: MutableList<String> = ArrayList(permissions.size)
- permissions.forEach {
- if (ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED) {
- needPermissions.add(it)
- }
- }
- if (needPermissions.isEmpty()) {
- val granted = IntArray(permissions.size)
- Arrays.fill(granted, PackageManager.PERMISSION_GRANTED)
- cb.invoke(permissions, granted)
- return
- }
- val idx = permissionRequestCounter++
- permissionRequestCallbacks.put(idx, cb)
- ActivityCompat.requestPermissions(this,
- needPermissions.toTypedArray(), idx)
- }
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (supportFragmentManager.findFragmentById(android.R.id.content) == null) {
@@ -69,16 +41,6 @@ class SettingsActivity : ThemeChangeAwareActivity() {
return super.onOptionsItemSelected(item)
}
- override fun onRequestPermissionsResult(requestCode: Int,
- permissions: Array<String>,
- grantResults: IntArray) {
- val f = permissionRequestCallbacks[requestCode]
- if (f != null) {
- permissionRequestCallbacks.remove(requestCode)
- f.invoke(permissions, grantResults)
- }
- }
-
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, key: String?) {
addPreferencesFromResource(R.xml.preferences)
diff --git a/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt b/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt
index 623e272f..aaea7703 100644
--- a/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt
+++ b/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt
@@ -4,9 +4,7 @@
*/
package com.wireguard.android.preference
-import android.Manifest
import android.content.Context
-import android.content.pm.PackageManager
import android.util.AttributeSet
import android.util.Log
import androidx.preference.Preference
@@ -43,7 +41,13 @@ class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference
if (configs.isEmpty()) {
throw IllegalArgumentException(context.getString(R.string.no_tunnels_error))
}
- val outputFile = DownloadsFileSaver.save(context, "wireguard-export.zip", "application/zip", true)
+ val outputFile = DownloadsFileSaver.save(activity, "wireguard-export.zip", "application/zip", true)
+ if (outputFile == null) {
+ withContext(Dispatchers.Main.immediate) {
+ isEnabled = true
+ }
+ return@withContext null
+ }
try {
ZipOutputStream(outputFile.outputStream).use { zip ->
for (i in configs.indices) {
@@ -82,17 +86,8 @@ class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference
when (it) {
// When we have successful authentication, or when there is no biometric hardware available.
is BiometricAuthenticator.Result.Success, is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> {
- if (DownloadsFileSaver.needsWriteExternalStoragePermission) {
- activity.ensurePermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { _, grantResults ->
- if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- isEnabled = false
- exportZip()
- }
- }
- } else {
- isEnabled = false
- exportZip()
- }
+ isEnabled = false
+ exportZip()
}
is BiometricAuthenticator.Result.Failure -> {
Snackbar.make(
diff --git a/ui/src/main/java/com/wireguard/android/util/DownloadsFileSaver.kt b/ui/src/main/java/com/wireguard/android/util/DownloadsFileSaver.kt
index 59cd526b..c8f4aa8d 100644
--- a/ui/src/main/java/com/wireguard/android/util/DownloadsFileSaver.kt
+++ b/ui/src/main/java/com/wireguard/android/util/DownloadsFileSaver.kt
@@ -4,67 +4,88 @@
*/
package com.wireguard.android.util
+import android.Manifest
import android.content.ContentValues
import android.content.Context
+import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.provider.MediaStore.MediaColumns
+import androidx.activity.ComponentActivity
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.core.content.ContextCompat
import com.wireguard.android.R
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStream
object DownloadsFileSaver {
- val needsWriteExternalStoragePermission = Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
-
@Throws(Exception::class)
- fun save(context: Context, name: String, mimeType: String?, overwriteExisting: Boolean) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- val contentResolver = context.contentResolver
- if (overwriteExisting)
- contentResolver.delete(MediaStore.Downloads.EXTERNAL_CONTENT_URI, String.format("%s = ?", MediaColumns.DISPLAY_NAME), arrayOf(name))
- val contentValues = ContentValues()
- contentValues.put(MediaColumns.DISPLAY_NAME, name)
- contentValues.put(MediaColumns.MIME_TYPE, mimeType)
- val contentUri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
- ?: throw IOException(context.getString(R.string.create_downloads_file_error))
- val contentStream = contentResolver.openOutputStream(contentUri)
- ?: throw IOException(context.getString(R.string.create_downloads_file_error))
- @Suppress("DEPRECATION") var cursor = contentResolver.query(contentUri, arrayOf(MediaColumns.DATA), null, null, null)
- var path: String? = null
- if (cursor != null) {
- try {
- if (cursor.moveToFirst())
- path = cursor.getString(0)
- } finally {
- cursor.close()
- }
- }
- if (path == null) {
- path = "Download/"
- cursor = contentResolver.query(contentUri, arrayOf(MediaColumns.DISPLAY_NAME), null, null, null)
+ suspend fun save(context: ComponentActivity, name: String, mimeType: String?, overwriteExisting: Boolean) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ withContext(Dispatchers.IO) {
+ val contentResolver = context.contentResolver
+ if (overwriteExisting)
+ contentResolver.delete(MediaStore.Downloads.EXTERNAL_CONTENT_URI, String.format("%s = ?", MediaColumns.DISPLAY_NAME), arrayOf(name))
+ val contentValues = ContentValues()
+ contentValues.put(MediaColumns.DISPLAY_NAME, name)
+ contentValues.put(MediaColumns.MIME_TYPE, mimeType)
+ val contentUri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
+ ?: throw IOException(context.getString(R.string.create_downloads_file_error))
+ val contentStream = contentResolver.openOutputStream(contentUri)
+ ?: throw IOException(context.getString(R.string.create_downloads_file_error))
+ @Suppress("DEPRECATION") var cursor = contentResolver.query(contentUri, arrayOf(MediaColumns.DATA), null, null, null)
+ var path: String? = null
if (cursor != null) {
try {
if (cursor.moveToFirst())
- path += cursor.getString(0)
+ path = cursor.getString(0)
} finally {
cursor.close()
}
}
+ if (path == null) {
+ path = "Download/"
+ cursor = contentResolver.query(contentUri, arrayOf(MediaColumns.DISPLAY_NAME), null, null, null)
+ if (cursor != null) {
+ try {
+ if (cursor.moveToFirst())
+ path += cursor.getString(0)
+ } finally {
+ cursor.close()
+ }
+ }
+ }
+ DownloadsFile(context, contentStream, path, contentUri)
}
- DownloadsFile(context, contentStream, path, contentUri)
} else {
- @Suppress("DEPRECATION") val path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
- val file = File(path, name)
- if (!path.isDirectory && !path.mkdirs())
- throw IOException(context.getString(R.string.create_output_dir_error))
- DownloadsFile(context, FileOutputStream(file), file.absolutePath, null)
+ withContext(Dispatchers.Main.immediate) {
+ if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ val futureGrant = CompletableDeferred<Boolean>()
+ val activityResult = context.registerForActivityResult(ActivityResultContracts.RequestPermission(), futureGrant::complete)
+ activityResult.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ val granted = futureGrant.await()
+ activityResult.unregister()
+ if (!granted)
+ return@withContext null
+ }
+ @Suppress("DEPRECATION") val path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
+ withContext(Dispatchers.IO) {
+ val file = File(path, name)
+ if (!path.isDirectory && !path.mkdirs())
+ throw IOException(context.getString(R.string.create_output_dir_error))
+ DownloadsFile(context, FileOutputStream(file), file.absolutePath, null)
+ }
+ }
}
class DownloadsFile(private val context: Context, val outputStream: OutputStream, val fileName: String, private val uri: Uri?) {
- fun delete() {
+ suspend fun delete() = withContext(Dispatchers.IO) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
context.contentResolver.delete(uri!!, null, null)
else