diff options
Diffstat (limited to 'ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt')
-rw-r--r-- | ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt b/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt new file mode 100644 index 00000000..52701157 --- /dev/null +++ b/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt @@ -0,0 +1,113 @@ +/* + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package com.wireguard.android.preference + +import android.content.Context +import android.util.AttributeSet +import android.util.Log +import androidx.preference.Preference +import com.google.android.material.snackbar.Snackbar +import com.wireguard.android.Application +import com.wireguard.android.R +import com.wireguard.android.util.AdminKnobs +import com.wireguard.android.util.BiometricAuthenticator +import com.wireguard.android.util.DownloadsFileSaver +import com.wireguard.android.util.ErrorMessages +import com.wireguard.android.util.activity +import com.wireguard.android.util.lifecycleScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.nio.charset.StandardCharsets +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream + +/** + * Preference implementing a button that asynchronously exports config zips. + */ +class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) { + private var exportedFilePath: String? = null + private val downloadsFileSaver = DownloadsFileSaver(activity) + + private fun exportZip() { + lifecycleScope.launch { + val tunnels = Application.getTunnelManager().getTunnels() + try { + exportedFilePath = withContext(Dispatchers.IO) { + val configs = tunnels.map { async(SupervisorJob()) { it.getConfigAsync() } }.awaitAll() + if (configs.isEmpty()) { + throw IllegalArgumentException(context.getString(R.string.no_tunnels_error)) + } + val outputFile = downloadsFileSaver.save("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) { + zip.putNextEntry(ZipEntry(tunnels[i].name + ".conf")) + zip.write(configs[i].toWgQuickString().toByteArray(StandardCharsets.UTF_8)) + } + zip.closeEntry() + } + } catch (e: Throwable) { + outputFile.delete() + throw e + } + outputFile.fileName + } + notifyChanged() + } catch (e: Throwable) { + val error = ErrorMessages[e] + val message = context.getString(R.string.zip_export_error, error) + Log.e(TAG, message, e) + Snackbar.make( + activity.findViewById(android.R.id.content), + message, Snackbar.LENGTH_LONG + ).show() + isEnabled = true + } + } + } + + override fun getSummary() = + if (exportedFilePath == null) context.getString(R.string.zip_export_summary) else context.getString(R.string.zip_export_success, exportedFilePath) + + override fun getTitle() = context.getString(R.string.zip_export_title) + + override fun onClick() { + if (AdminKnobs.disableConfigExport) return + val fragment = activity.supportFragmentManager.fragments.first() + BiometricAuthenticator.authenticate(R.string.biometric_prompt_zip_exporter_title, fragment) { + when (it) { + // When we have successful authentication, or when there is no biometric hardware available. + is BiometricAuthenticator.Result.Success, is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> { + isEnabled = false + exportZip() + } + + is BiometricAuthenticator.Result.Failure -> { + Snackbar.make( + activity.findViewById(android.R.id.content), + it.message, + Snackbar.LENGTH_SHORT + ).show() + } + + is BiometricAuthenticator.Result.Cancelled -> {} + } + } + } + + companion object { + private const val TAG = "WireGuard/ZipExporterPreference" + } +} |