aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2019-10-12 13:51:51 +0200
committerJason A. Donenfeld <Jason@zx2c4.com>2019-10-12 18:55:28 +0200
commit4fb19dacc9d95c1a9de1fc3a599acd26697c355b (patch)
tree21490ef77ba925811f09fb1964f454e816733921
parentpreferences: add donation link (diff)
downloadwireguard-android-4fb19dacc9d95c1a9de1fc3a599acd26697c355b.tar.xz
wireguard-android-4fb19dacc9d95c1a9de1fc3a599acd26697c355b.zip
export: use content resolver on android Q+
-rw-r--r--app/src/main/java/com/wireguard/android/preference/LogExporterPreference.java44
-rw-r--r--app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java19
-rw-r--r--app/src/main/java/com/wireguard/android/util/DownloadsFileSaver.java96
-rw-r--r--app/src/main/java/com/wireguard/config/InetAddresses.java2
-rw-r--r--app/src/main/res/values/strings.xml2
5 files changed, 125 insertions, 38 deletions
diff --git a/app/src/main/java/com/wireguard/android/preference/LogExporterPreference.java b/app/src/main/java/com/wireguard/android/preference/LogExporterPreference.java
index b598827..565854b 100644
--- a/app/src/main/java/com/wireguard/android/preference/LogExporterPreference.java
+++ b/app/src/main/java/com/wireguard/android/preference/LogExporterPreference.java
@@ -8,22 +8,21 @@ package com.wireguard.android.preference;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.os.Environment;
import androidx.annotation.Nullable;
import com.google.android.material.snackbar.Snackbar;
import androidx.preference.Preference;
+
import android.util.AttributeSet;
import android.util.Log;
import com.wireguard.android.Application;
import com.wireguard.android.R;
+import com.wireguard.android.util.DownloadsFileSaver;
+import com.wireguard.android.util.DownloadsFileSaver.DownloadsFile;
import com.wireguard.android.util.ErrorMessages;
import com.wireguard.android.util.FragmentUtils;
import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
import java.io.InputStreamReader;
/**
@@ -41,36 +40,33 @@ public class LogExporterPreference extends Preference {
private void exportLog() {
Application.getAsyncWorker().supplyAsync(() -> {
- final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
- final File file = new File(path, "wireguard-log.txt");
- if (!path.isDirectory() && !path.mkdirs())
- throw new IOException(
- getContext().getString(R.string.create_output_dir_error));
-
- /* We would like to simply run `builder.redirectOutput(file);`, but this is API 26.
- * Instead we have to do this dance, since logcat appends.
- */
- new FileOutputStream(file).close();
-
+ DownloadsFile outputFile = DownloadsFileSaver.save(getContext(), "wireguard-log.txt", "text/plain", true);
try {
final Process process = Runtime.getRuntime().exec(new String[]{
- "logcat", "-b", "all", "-d", "-v", "threadtime", "-f", file.getAbsolutePath(), "*:V"});
- if (process.waitFor() != 0) {
- try (final BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
+ "logcat", "-b", "all", "-d", "-v", "threadtime", "*:V"});
+ try (final BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream()));
+ final BufferedReader stderr = new BufferedReader(new InputStreamReader(process.getErrorStream())))
+ {
+ String line;
+ while ((line = stdout.readLine()) != null) {
+ outputFile.getOutputStream().write(line.getBytes());
+ outputFile.getOutputStream().write('\n');
+ }
+ outputFile.getOutputStream().close();
+ stdout.close();
+ if (process.waitFor() != 0) {
final StringBuilder errors = new StringBuilder();
- errors.append("Unable to run logcat: ");
- String line;
- while ((line = reader.readLine()) != null)
+ errors.append(R.string.logcat_error);
+ while ((line = stderr.readLine()) != null)
errors.append(line);
throw new Exception(errors.toString());
}
}
} catch (final Exception e) {
- // noinspection ResultOfMethodCallIgnored
- file.delete();
+ outputFile.delete();
throw e;
}
- return file.getAbsolutePath();
+ return outputFile.getFileName();
}).whenComplete(this::exportLogComplete);
}
diff --git a/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java b/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java
index fa5093c..efda91b 100644
--- a/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java
+++ b/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java
@@ -8,7 +8,6 @@ package com.wireguard.android.preference;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.os.Environment;
import androidx.annotation.Nullable;
import com.google.android.material.snackbar.Snackbar;
import androidx.preference.Preference;
@@ -18,13 +17,12 @@ import android.util.Log;
import com.wireguard.android.Application;
import com.wireguard.android.R;
import com.wireguard.android.model.Tunnel;
+import com.wireguard.android.util.DownloadsFileSaver;
+import com.wireguard.android.util.DownloadsFileSaver.DownloadsFile;
import com.wireguard.android.util.ErrorMessages;
import com.wireguard.android.util.FragmentUtils;
import com.wireguard.config.Config;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@@ -63,12 +61,8 @@ public class ZipExporterPreference extends Preference {
.whenComplete((ignored1, exception) -> Application.getAsyncWorker().supplyAsync(() -> {
if (exception != null)
throw exception;
- final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
- final File file = new File(path, "wireguard-export.zip");
- if (!path.isDirectory() && !path.mkdirs())
- throw new IOException(
- getContext().getString(R.string.create_output_dir_error));
- try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(file))) {
+ DownloadsFile outputFile = DownloadsFileSaver.save(getContext(), "wireguard-export.zip", "application/zip", true);
+ try (ZipOutputStream zip = new ZipOutputStream(outputFile.getOutputStream())) {
for (int i = 0; i < futureConfigs.size(); ++i) {
zip.putNextEntry(new ZipEntry(tunnels.get(i).getName() + ".conf"));
zip.write(futureConfigs.get(i).getNow(null).
@@ -76,11 +70,10 @@ public class ZipExporterPreference extends Preference {
}
zip.closeEntry();
} catch (final Exception e) {
- // noinspection ResultOfMethodCallIgnored
- file.delete();
+ outputFile.delete();
throw e;
}
- return file.getAbsolutePath();
+ return outputFile.getFileName();
}).whenComplete(this::exportZipComplete));
}
diff --git a/app/src/main/java/com/wireguard/android/util/DownloadsFileSaver.java b/app/src/main/java/com/wireguard/android/util/DownloadsFileSaver.java
new file mode 100644
index 0000000..efe09f3
--- /dev/null
+++ b/app/src/main/java/com/wireguard/android/util/DownloadsFileSaver.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright © 2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.android.util;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.provider.MediaStore.MediaColumns;
+
+import com.wireguard.android.R;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class DownloadsFileSaver {
+
+ public static class DownloadsFile {
+ private Context context;
+ private OutputStream outputStream;
+ private String fileName;
+ private Uri uri;
+
+ private DownloadsFile(final Context context, final OutputStream outputStream, final String fileName, final Uri uri) {
+ this.context = context;
+ this.outputStream = outputStream;
+ this.fileName = fileName;
+ this.uri = uri;
+ }
+
+ public OutputStream getOutputStream() { return outputStream; }
+ public String getFileName() { return fileName; }
+
+ public void delete() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
+ context.getContentResolver().delete(uri, null, null);
+ else
+ new File(fileName).delete();
+ }
+ }
+
+ public static DownloadsFile save(final Context context, final String name, final String mimeType, final boolean overwriteExisting) throws Exception {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ final ContentResolver contentResolver = context.getContentResolver();
+ if (overwriteExisting)
+ contentResolver.delete(MediaStore.Downloads.EXTERNAL_CONTENT_URI, String.format("%s = ?", MediaColumns.DISPLAY_NAME), new String[]{name});
+ final ContentValues contentValues = new ContentValues();
+ contentValues.put(MediaColumns.DISPLAY_NAME, name);
+ contentValues.put(MediaColumns.MIME_TYPE, mimeType);
+ final Uri contentUri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues);
+ if (contentUri == null)
+ throw new IOException(context.getString(R.string.create_downloads_file_error));
+ final OutputStream contentStream = contentResolver.openOutputStream(contentUri);
+ if (contentStream == null)
+ throw new IOException(context.getString(R.string.create_downloads_file_error));
+ Cursor cursor = contentResolver.query(contentUri, new String[]{MediaColumns.DATA}, null, null, null);
+ String path = null;
+ if (cursor != null) {
+ try {
+ if (cursor.moveToFirst())
+ path = cursor.getString(0);
+ } finally {
+ cursor.close();
+ }
+ }
+ if (path == null) {
+ path = "Download/";
+ cursor = contentResolver.query(contentUri, new String[]{MediaColumns.DISPLAY_NAME}, null, null, null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToFirst())
+ path += cursor.getString(0);
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+ return new DownloadsFile(context, contentStream, path, contentUri);
+ } else {
+ final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
+ final File file = new File(path, name);
+ if (!path.isDirectory() && !path.mkdirs())
+ throw new IOException(context.getString(R.string.create_output_dir_error));
+ return new DownloadsFile(context, new FileOutputStream(file), file.getAbsolutePath(), null);
+ }
+ }
+}
diff --git a/app/src/main/java/com/wireguard/config/InetAddresses.java b/app/src/main/java/com/wireguard/config/InetAddresses.java
index 864082e..6396492 100644
--- a/app/src/main/java/com/wireguard/config/InetAddresses.java
+++ b/app/src/main/java/com/wireguard/config/InetAddresses.java
@@ -46,7 +46,7 @@ public final class InetAddresses {
if (address.isEmpty())
throw new ParseException(InetAddress.class, address, "Empty address");
try {
- if (Build.VERSION.SDK_INT < 29)
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
return (InetAddress) getParserMethod().invoke(null, address);
else
return android.net.InetAddresses.parseNumericAddress(address);
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index bed5abc..7b5dcf7 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -59,6 +59,7 @@
<string name="create_from_file">Create from file or archive</string>
<string name="create_from_qr_code">Create from QR code</string>
<string name="create_output_dir_error">Cannot create output directory</string>
+ <string name="create_downloads_file_error">Cannot create file in downloads directory</string>
<string name="create_temp_dir_error">Cannot create local temporary directory</string>
<string name="create_tunnel">Create Tunnel</string>
<string name="dark_theme_summary_off">Currently using light (day) theme</string>
@@ -96,6 +97,7 @@
<string name="log_export_success">Saved to “%s”</string>
<string name="log_export_summary">Log file will be saved to downloads folder</string>
<string name="log_export_title">Export log file</string>
+ <string name="logcat_error">Unable to run logcat: </string>
<string name="module_version_error">Unable to determine kernel module version</string>
<string name="mtu">MTU</string>
<string name="multiple_tunnels_error">Only one userspace tunnel can run at a time</string>