export: use content resolver on android Q+
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
parent
d8bad72fd6
commit
7fbe5349a2
@ -8,22 +8,21 @@ package com.wireguard.android.preference;
|
|||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Environment;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
|
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.wireguard.android.Application;
|
import com.wireguard.android.Application;
|
||||||
import com.wireguard.android.R;
|
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.ErrorMessages;
|
||||||
import com.wireguard.android.util.FragmentUtils;
|
import com.wireguard.android.util.FragmentUtils;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -41,36 +40,33 @@ public class LogExporterPreference extends Preference {
|
|||||||
|
|
||||||
private void exportLog() {
|
private void exportLog() {
|
||||||
Application.getAsyncWorker().supplyAsync(() -> {
|
Application.getAsyncWorker().supplyAsync(() -> {
|
||||||
final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
DownloadsFile outputFile = DownloadsFileSaver.save(getContext(), "wireguard-log.txt", "text/plain", true);
|
||||||
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();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final Process process = Runtime.getRuntime().exec(new String[]{
|
final Process process = Runtime.getRuntime().exec(new String[]{
|
||||||
"logcat", "-b", "all", "-d", "-v", "threadtime", "-f", file.getAbsolutePath(), "*:V"});
|
"logcat", "-b", "all", "-d", "-v", "threadtime", "*:V"});
|
||||||
if (process.waitFor() != 0) {
|
try (final BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||||
try (final BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
|
final BufferedReader stderr = new BufferedReader(new InputStreamReader(process.getErrorStream())))
|
||||||
final StringBuilder errors = new StringBuilder();
|
{
|
||||||
errors.append("Unable to run logcat: ");
|
|
||||||
String line;
|
String line;
|
||||||
while ((line = reader.readLine()) != null)
|
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(R.string.logcat_error);
|
||||||
|
while ((line = stderr.readLine()) != null)
|
||||||
errors.append(line);
|
errors.append(line);
|
||||||
throw new Exception(errors.toString());
|
throw new Exception(errors.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
// noinspection ResultOfMethodCallIgnored
|
outputFile.delete();
|
||||||
file.delete();
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
return file.getAbsolutePath();
|
return outputFile.getFileName();
|
||||||
}).whenComplete(this::exportLogComplete);
|
}).whenComplete(this::exportLogComplete);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ package com.wireguard.android.preference;
|
|||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Environment;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
@ -18,13 +17,12 @@ import android.util.Log;
|
|||||||
import com.wireguard.android.Application;
|
import com.wireguard.android.Application;
|
||||||
import com.wireguard.android.R;
|
import com.wireguard.android.R;
|
||||||
import com.wireguard.android.model.Tunnel;
|
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.ErrorMessages;
|
||||||
import com.wireguard.android.util.FragmentUtils;
|
import com.wireguard.android.util.FragmentUtils;
|
||||||
import com.wireguard.config.Config;
|
import com.wireguard.config.Config;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -63,12 +61,8 @@ public class ZipExporterPreference extends Preference {
|
|||||||
.whenComplete((ignored1, exception) -> Application.getAsyncWorker().supplyAsync(() -> {
|
.whenComplete((ignored1, exception) -> Application.getAsyncWorker().supplyAsync(() -> {
|
||||||
if (exception != null)
|
if (exception != null)
|
||||||
throw exception;
|
throw exception;
|
||||||
final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
DownloadsFile outputFile = DownloadsFileSaver.save(getContext(), "wireguard-export.zip", "application/zip", true);
|
||||||
final File file = new File(path, "wireguard-export.zip");
|
try (ZipOutputStream zip = new ZipOutputStream(outputFile.getOutputStream())) {
|
||||||
if (!path.isDirectory() && !path.mkdirs())
|
|
||||||
throw new IOException(
|
|
||||||
getContext().getString(R.string.create_output_dir_error));
|
|
||||||
try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(file))) {
|
|
||||||
for (int i = 0; i < futureConfigs.size(); ++i) {
|
for (int i = 0; i < futureConfigs.size(); ++i) {
|
||||||
zip.putNextEntry(new ZipEntry(tunnels.get(i).getName() + ".conf"));
|
zip.putNextEntry(new ZipEntry(tunnels.get(i).getName() + ".conf"));
|
||||||
zip.write(futureConfigs.get(i).getNow(null).
|
zip.write(futureConfigs.get(i).getNow(null).
|
||||||
@ -76,11 +70,10 @@ public class ZipExporterPreference extends Preference {
|
|||||||
}
|
}
|
||||||
zip.closeEntry();
|
zip.closeEntry();
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
// noinspection ResultOfMethodCallIgnored
|
outputFile.delete();
|
||||||
file.delete();
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
return file.getAbsolutePath();
|
return outputFile.getFileName();
|
||||||
}).whenComplete(this::exportZipComplete));
|
}).whenComplete(this::exportZipComplete));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -46,7 +46,7 @@ public final class InetAddresses {
|
|||||||
if (address.isEmpty())
|
if (address.isEmpty())
|
||||||
throw new ParseException(InetAddress.class, address, "Empty address");
|
throw new ParseException(InetAddress.class, address, "Empty address");
|
||||||
try {
|
try {
|
||||||
if (Build.VERSION.SDK_INT < 29)
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
|
||||||
return (InetAddress) getParserMethod().invoke(null, address);
|
return (InetAddress) getParserMethod().invoke(null, address);
|
||||||
else
|
else
|
||||||
return android.net.InetAddresses.parseNumericAddress(address);
|
return android.net.InetAddresses.parseNumericAddress(address);
|
||||||
|
@ -59,6 +59,7 @@
|
|||||||
<string name="create_from_file">Create from file or archive</string>
|
<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_from_qr_code">Create from QR code</string>
|
||||||
<string name="create_output_dir_error">Cannot create output directory</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_temp_dir_error">Cannot create local temporary directory</string>
|
||||||
<string name="create_tunnel">Create Tunnel</string>
|
<string name="create_tunnel">Create Tunnel</string>
|
||||||
<string name="dark_theme_summary_off">Currently using light (day) theme</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_success">Saved to “%s”</string>
|
||||||
<string name="log_export_summary">Log file will be saved to downloads folder</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="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="module_version_error">Unable to determine kernel module version</string>
|
||||||
<string name="mtu">MTU</string>
|
<string name="mtu">MTU</string>
|
||||||
<string name="multiple_tunnels_error">Only one userspace tunnel can run at a time</string>
|
<string name="multiple_tunnels_error">Only one userspace tunnel can run at a time</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user