From 7d48bef70a56d4370856eedab619b1f83ac3d0d0 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Mon, 9 Mar 2020 19:06:11 +0530 Subject: Rename app module to ui Signed-off-by: Harsh Shandilya --- .../android/preference/LogExporterPreference.java | 112 +++++++++++++++++++ .../preference/ModuleDownloaderPreference.java | 92 ++++++++++++++++ .../preference/ToolsInstallerPreference.java | 102 ++++++++++++++++++ .../android/preference/VersionPreference.java | 71 ++++++++++++ .../android/preference/ZipExporterPreference.java | 119 +++++++++++++++++++++ 5 files changed, 496 insertions(+) create mode 100644 ui/src/main/java/com/wireguard/android/preference/LogExporterPreference.java create mode 100644 ui/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.java create mode 100644 ui/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.java create mode 100644 ui/src/main/java/com/wireguard/android/preference/VersionPreference.java create mode 100644 ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java (limited to 'ui/src/main/java/com/wireguard/android/preference') diff --git a/ui/src/main/java/com/wireguard/android/preference/LogExporterPreference.java b/ui/src/main/java/com/wireguard/android/preference/LogExporterPreference.java new file mode 100644 index 00000000..565854b4 --- /dev/null +++ b/ui/src/main/java/com/wireguard/android/preference/LogExporterPreference.java @@ -0,0 +1,112 @@ +/* + * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.wireguard.android.preference; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +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.InputStreamReader; + +/** + * Preference implementing a button that asynchronously exports logs. + */ + +public class LogExporterPreference extends Preference { + private static final String TAG = "WireGuard/" + LogExporterPreference.class.getSimpleName(); + + @Nullable private String exportedFilePath; + + public LogExporterPreference(final Context context, final AttributeSet attrs) { + super(context, attrs); + } + + private void exportLog() { + Application.getAsyncWorker().supplyAsync(() -> { + 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", "*: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(R.string.logcat_error); + while ((line = stderr.readLine()) != null) + errors.append(line); + throw new Exception(errors.toString()); + } + } + } catch (final Exception e) { + outputFile.delete(); + throw e; + } + return outputFile.getFileName(); + }).whenComplete(this::exportLogComplete); + } + + private void exportLogComplete(final String filePath, @Nullable final Throwable throwable) { + if (throwable != null) { + final String error = ErrorMessages.get(throwable); + final String message = getContext().getString(R.string.log_export_error, error); + Log.e(TAG, message, throwable); + Snackbar.make( + FragmentUtils.getPrefActivity(this).findViewById(android.R.id.content), + message, Snackbar.LENGTH_LONG).show(); + setEnabled(true); + } else { + exportedFilePath = filePath; + notifyChanged(); + } + } + + @Override + public CharSequence getSummary() { + return exportedFilePath == null ? + getContext().getString(R.string.log_export_summary) : + getContext().getString(R.string.log_export_success, exportedFilePath); + } + + @Override + public CharSequence getTitle() { + return getContext().getString(R.string.log_export_title); + } + + @Override + protected void onClick() { + FragmentUtils.getPrefActivity(this).ensurePermissions( + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + (permissions, granted) -> { + if (granted.length > 0 && granted[0] == PackageManager.PERMISSION_GRANTED) { + setEnabled(false); + exportLog(); + } + }); + } + +} diff --git a/ui/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.java b/ui/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.java new file mode 100644 index 00000000..aac649dd --- /dev/null +++ b/ui/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.java @@ -0,0 +1,92 @@ +/* + * Copyright © 2019 WireGuard LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.wireguard.android.preference; + +import android.content.Context; +import android.content.Intent; +import android.system.OsConstants; +import android.util.AttributeSet; +import android.widget.Toast; + +import com.wireguard.android.Application; +import com.wireguard.android.R; +import com.wireguard.android.util.ErrorMessages; +import com.wireguard.android.util.ModuleLoader; +import com.wireguard.android.util.ToolsInstaller; + +import androidx.annotation.Nullable; +import androidx.preference.Preference; + +public class ModuleDownloaderPreference extends Preference { + private State state = State.INITIAL; + + public ModuleDownloaderPreference(final Context context, final AttributeSet attrs) { + super(context, attrs); + } + + @Override + public CharSequence getSummary() { + return getContext().getString(state.messageResourceId); + } + + @Override + public CharSequence getTitle() { + return getContext().getString(R.string.module_installer_title); + } + + @Override + protected void onClick() { + setState(State.WORKING); + Application.getAsyncWorker().supplyAsync(Application.getModuleLoader()::download).whenComplete(this::onDownloadResult); + } + + private void onDownloadResult(final Integer result, @Nullable final Throwable throwable) { + if (throwable != null) { + setState(State.FAILURE); + Toast.makeText(getContext(), ErrorMessages.get(throwable), Toast.LENGTH_LONG).show(); + } else if (result == OsConstants.ENOENT) + setState(State.NOTFOUND); + else if (result == OsConstants.EXIT_SUCCESS) { + setState(State.SUCCESS); + Application.getAsyncWorker().runAsync(() -> { + Thread.sleep(1000 * 5); + Intent i = getContext().getPackageManager().getLaunchIntentForPackage(getContext().getPackageName()); + if (i == null) + return; + i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Application.get().startActivity(i); + System.exit(0); + }); + } else + setState(State.FAILURE); + } + + private void setState(final State state) { + if (this.state == state) + return; + this.state = state; + if (isEnabled() != state.shouldEnableView) + setEnabled(state.shouldEnableView); + notifyChanged(); + } + + private enum State { + INITIAL(R.string.module_installer_initial, true), + FAILURE(R.string.module_installer_error, true), + WORKING(R.string.module_installer_working, false), + SUCCESS(R.string.module_installer_success, false), + NOTFOUND(R.string.module_installer_not_found, false); + + private final int messageResourceId; + private final boolean shouldEnableView; + + State(final int messageResourceId, final boolean shouldEnableView) { + this.messageResourceId = messageResourceId; + this.shouldEnableView = shouldEnableView; + } + } +} diff --git a/ui/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.java b/ui/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.java new file mode 100644 index 00000000..78a7497b --- /dev/null +++ b/ui/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.java @@ -0,0 +1,102 @@ +/* + * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.wireguard.android.preference; + +import android.content.Context; +import androidx.annotation.Nullable; +import androidx.preference.Preference; +import android.util.AttributeSet; + +import com.wireguard.android.Application; +import com.wireguard.android.R; +import com.wireguard.android.util.ToolsInstaller; + +/** + * Preference implementing a button that asynchronously runs {@code ToolsInstaller} and displays the + * result as the preference summary. + */ + +public class ToolsInstallerPreference extends Preference { + private State state = State.INITIAL; + + public ToolsInstallerPreference(final Context context, final AttributeSet attrs) { + super(context, attrs); + } + + @Override + public CharSequence getSummary() { + return getContext().getString(state.messageResourceId); + } + + @Override + public CharSequence getTitle() { + return getContext().getString(R.string.tools_installer_title); + } + + @Override + public void onAttached() { + super.onAttached(); + Application.getAsyncWorker().supplyAsync(Application.getToolsInstaller()::areInstalled).whenComplete(this::onCheckResult); + } + + private void onCheckResult(final int state, @Nullable final Throwable throwable) { + if (throwable != null || state == ToolsInstaller.ERROR) + setState(State.INITIAL); + else if ((state & ToolsInstaller.YES) == ToolsInstaller.YES) + setState(State.ALREADY); + else if ((state & (ToolsInstaller.MAGISK | ToolsInstaller.NO)) == (ToolsInstaller.MAGISK | ToolsInstaller.NO)) + setState(State.INITIAL_MAGISK); + else if ((state & (ToolsInstaller.SYSTEM | ToolsInstaller.NO)) == (ToolsInstaller.SYSTEM | ToolsInstaller.NO)) + setState(State.INITIAL_SYSTEM); + else + setState(State.INITIAL); + } + + @Override + protected void onClick() { + setState(State.WORKING); + Application.getAsyncWorker().supplyAsync(Application.getToolsInstaller()::install).whenComplete(this::onInstallResult); + } + + private void onInstallResult(final Integer result, @Nullable final Throwable throwable) { + if (throwable != null) + setState(State.FAILURE); + else if ((result & (ToolsInstaller.YES | ToolsInstaller.MAGISK)) == (ToolsInstaller.YES | ToolsInstaller.MAGISK)) + setState(State.SUCCESS_MAGISK); + else if ((result & (ToolsInstaller.YES | ToolsInstaller.SYSTEM)) == (ToolsInstaller.YES | ToolsInstaller.SYSTEM)) + setState(State.SUCCESS_SYSTEM); + else + setState(State.FAILURE); + } + + private void setState(final State state) { + if (this.state == state) + return; + this.state = state; + if (isEnabled() != state.shouldEnableView) + setEnabled(state.shouldEnableView); + notifyChanged(); + } + + private enum State { + INITIAL(R.string.tools_installer_initial, true), + ALREADY(R.string.tools_installer_already, false), + FAILURE(R.string.tools_installer_failure, true), + WORKING(R.string.tools_installer_working, false), + INITIAL_SYSTEM(R.string.tools_installer_initial_system, true), + SUCCESS_SYSTEM(R.string.tools_installer_success_system, false), + INITIAL_MAGISK(R.string.tools_installer_initial_magisk, true), + SUCCESS_MAGISK(R.string.tools_installer_success_magisk, false); + + private final int messageResourceId; + private final boolean shouldEnableView; + + State(final int messageResourceId, final boolean shouldEnableView) { + this.messageResourceId = messageResourceId; + this.shouldEnableView = shouldEnableView; + } + } +} diff --git a/ui/src/main/java/com/wireguard/android/preference/VersionPreference.java b/ui/src/main/java/com/wireguard/android/preference/VersionPreference.java new file mode 100644 index 00000000..7e95a8ae --- /dev/null +++ b/ui/src/main/java/com/wireguard/android/preference/VersionPreference.java @@ -0,0 +1,71 @@ +/* + * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.wireguard.android.preference; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import androidx.annotation.Nullable; +import androidx.preference.Preference; +import android.util.AttributeSet; + +import com.wireguard.android.Application; +import com.wireguard.android.BuildConfig; +import com.wireguard.android.R; +import com.wireguard.android.backend.Backend; +import com.wireguard.android.backend.GoBackend; +import com.wireguard.android.backend.WgQuickBackend; + +import java.util.Locale; + +public class VersionPreference extends Preference { + @Nullable private String versionSummary; + + private String getBackendPrettyName(final Context context, final Backend backend) { + if (backend instanceof GoBackend) + return context.getString(R.string.type_name_kernel_module); + if (backend instanceof WgQuickBackend) + return context.getString(R.string.type_name_go_userspace); + return ""; + } + + public VersionPreference(final Context context, final AttributeSet attrs) { + super(context, attrs); + + Application.getBackendAsync().thenAccept(backend -> { + versionSummary = getContext().getString(R.string.version_summary_checking, getBackendPrettyName(context, backend).toLowerCase(Locale.ENGLISH)); + Application.getAsyncWorker().supplyAsync(backend::getVersion).whenComplete((version, exception) -> { + versionSummary = exception == null + ? getContext().getString(R.string.version_summary, getBackendPrettyName(context, backend), version) + : getContext().getString(R.string.version_summary_unknown, getBackendPrettyName(context, backend).toLowerCase(Locale.ENGLISH)); + notifyChanged(); + }); + }); + } + + @Nullable + @Override + public CharSequence getSummary() { + return versionSummary; + } + + @Override + public CharSequence getTitle() { + return getContext().getString(R.string.version_title, BuildConfig.VERSION_NAME); + } + + @Override + protected void onClick() { + final Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse("https://www.wireguard.com/")); + try { + getContext().startActivity(intent); + } catch (final ActivityNotFoundException ignored) { + } + } + +} diff --git a/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java b/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java new file mode 100644 index 00000000..3af412a5 --- /dev/null +++ b/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java @@ -0,0 +1,119 @@ +/* + * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.wireguard.android.preference; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +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.model.ObservableTunnel; +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.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import java9.util.concurrent.CompletableFuture; + +/** + * Preference implementing a button that asynchronously exports config zips. + */ + +public class ZipExporterPreference extends Preference { + private static final String TAG = "WireGuard/" + ZipExporterPreference.class.getSimpleName(); + + @Nullable private String exportedFilePath; + + public ZipExporterPreference(final Context context, final AttributeSet attrs) { + super(context, attrs); + } + + private void exportZip() { + Application.getTunnelManager().getTunnels().thenAccept(this::exportZip); + } + + private void exportZip(final List tunnels) { + final List> futureConfigs = new ArrayList<>(tunnels.size()); + for (final ObservableTunnel tunnel : tunnels) + futureConfigs.add(tunnel.getConfigAsync().toCompletableFuture()); + if (futureConfigs.isEmpty()) { + exportZipComplete(null, new IllegalArgumentException( + getContext().getString(R.string.no_tunnels_error))); + return; + } + CompletableFuture.allOf(futureConfigs.toArray(new CompletableFuture[futureConfigs.size()])) + .whenComplete((ignored1, exception) -> Application.getAsyncWorker().supplyAsync(() -> { + if (exception != null) + throw exception; + 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). + toWgQuickString().getBytes(StandardCharsets.UTF_8)); + } + zip.closeEntry(); + } catch (final Exception e) { + outputFile.delete(); + throw e; + } + return outputFile.getFileName(); + }).whenComplete(this::exportZipComplete)); + } + + private void exportZipComplete(@Nullable final String filePath, @Nullable final Throwable throwable) { + if (throwable != null) { + final String error = ErrorMessages.get(throwable); + final String message = getContext().getString(R.string.zip_export_error, error); + Log.e(TAG, message, throwable); + Snackbar.make( + FragmentUtils.getPrefActivity(this).findViewById(android.R.id.content), + message, Snackbar.LENGTH_LONG).show(); + setEnabled(true); + } else { + exportedFilePath = filePath; + notifyChanged(); + } + } + + @Override + public CharSequence getSummary() { + return exportedFilePath == null ? + getContext().getString(R.string.zip_export_summary) : + getContext().getString(R.string.zip_export_success, exportedFilePath); + } + + @Override + public CharSequence getTitle() { + return getContext().getString(R.string.zip_export_title); + } + + @Override + protected void onClick() { + FragmentUtils.getPrefActivity(this).ensurePermissions( + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + (permissions, granted) -> { + if (granted.length > 0 && granted[0] == PackageManager.PERMISSION_GRANTED) { + setEnabled(false); + exportZip(); + } + }); + } + +} -- cgit v1.2.3-59-g8ed1b