diff --git a/app/build.gradle b/app/build.gradle
index 9729af65..83638cc9 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -68,6 +68,7 @@ ext {
supportLibsVersion = '27.1.1'
streamsupportVersion = '1.6.0'
jsr305Version = '3.0.2'
+ zxingEmbeddedVersion = '3.6.0'
}
dependencies {
@@ -80,6 +81,7 @@ dependencies {
implementation "net.sourceforge.streamsupport:android-retrofuture:$streamsupportVersion"
implementation "net.sourceforge.streamsupport:android-retrostreams:$streamsupportVersion"
implementation "com.google.code.findbugs:jsr305:$jsr305Version"
+ implementation "com.journeyapps:zxing-android-embedded:$zxingEmbeddedVersion"
}
tasks.withType(JavaCompile) {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e422c566..b97dbc7d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,6 +4,7 @@
package="com.wireguard.android"
android:installLocation="internalOnly">
+
@@ -50,6 +51,11 @@
android:label="@string/create_activity_title"
android:parentActivityName=".activity.MainActivity" />
+
+
diff --git a/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java b/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java
new file mode 100644
index 00000000..1a3ec0d2
--- /dev/null
+++ b/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright © 2018 Eric Kuck .
+ * Copyright © 2018 Jason A. Donenfeld . All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.android.fragment;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.DialogFragment;
+import android.support.v7.app.AlertDialog;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+
+import com.wireguard.android.Application;
+import com.wireguard.android.R;
+import com.wireguard.android.databinding.ConfigNamingDialogFragmentBinding;
+import com.wireguard.config.Config;
+
+import java.io.IOException;
+import java.util.Objects;
+
+public class ConfigNamingDialogFragment extends DialogFragment {
+
+ private static final String KEY_CONFIG_TEXT = "config_text";
+
+ @Nullable private Config config;
+ @Nullable private ConfigNamingDialogFragmentBinding binding;
+ @Nullable private InputMethodManager imm;
+
+ public static ConfigNamingDialogFragment newInstance(final String configText) {
+ final Bundle extras = new Bundle();
+ extras.putString(KEY_CONFIG_TEXT, configText);
+ final ConfigNamingDialogFragment fragment = new ConfigNamingDialogFragment();
+ fragment.setArguments(extras);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(@Nullable final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ try {
+ config = Config.from(getArguments().getString(KEY_CONFIG_TEXT));
+ } catch (final IOException exception) {
+ throw new RuntimeException("Invalid config passed to " + getClass().getSimpleName(), exception);
+ }
+ }
+
+ @Override public void onResume() {
+ super.onResume();
+
+ final AlertDialog dialog = (AlertDialog) getDialog();
+ if (dialog != null) {
+ dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> createTunnelAndDismiss());
+
+ setKeyboardVisible(true);
+ }
+ }
+
+ @Override
+ public Dialog onCreateDialog(final Bundle savedInstanceState) {
+ final Activity activity = getActivity();
+
+ imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(activity);
+ alertDialogBuilder.setTitle(R.string.create_tunnel);
+
+ binding = ConfigNamingDialogFragmentBinding.inflate(getActivity().getLayoutInflater(), null, false);
+ binding.executePendingBindings();
+ alertDialogBuilder.setView(binding.getRoot());
+
+ alertDialogBuilder.setPositiveButton(R.string.create_tunnel, null);
+ alertDialogBuilder.setNegativeButton(R.string.cancel, (dialog, which) -> dismiss());
+
+ return alertDialogBuilder.create();
+ }
+
+ @Override
+ public void dismiss() {
+ setKeyboardVisible(false);
+ super.dismiss();
+ }
+
+ private void createTunnelAndDismiss() {
+ if (binding != null) {
+ final String name = binding.tunnelNameText.getText().toString();
+
+ Application.getTunnelManager().create(name, config).whenComplete((tunnel, throwable) -> {
+ if (tunnel != null) {
+ dismiss();
+ } else {
+ binding.tunnelNameTextLayout.setError(throwable.getMessage());
+ }
+ });
+ }
+ }
+
+ private void setKeyboardVisible(final boolean visible) {
+ Objects.requireNonNull(imm);
+
+ if (visible) {
+ imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
+ } else if (binding != null) {
+ imm.hideSoftInputFromWindow(binding.tunnelNameText.getWindowToken(), 0);
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java
index d0019c91..23e449b6 100644
--- a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java
+++ b/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java
@@ -18,6 +18,7 @@ import android.provider.OpenableColumns;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
+import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.view.ActionMode;
import android.util.Log;
@@ -27,6 +28,8 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import com.google.zxing.integration.android.IntentIntegrator;
+import com.google.zxing.integration.android.IntentResult;
import com.wireguard.android.Application;
import com.wireguard.android.R;
import com.wireguard.android.activity.TunnelCreatorActivity;
@@ -39,6 +42,7 @@ import com.wireguard.android.widget.fab.FloatingActionsMenuRecyclerViewScrollLis
import com.wireguard.config.Config;
import java.io.BufferedReader;
+import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -72,6 +76,22 @@ public class TunnelListFragment extends BaseFragment {
return false;
}
+ private void importTunnel(@NonNull final String configText) {
+ try {
+ // Ensure the config text is parseable before proceeding…
+ Config.from(configText);
+
+ // Config text is valid, now create the tunnel…
+ final FragmentManager fragmentManager = getFragmentManager();
+ if (fragmentManager != null) {
+ final ConfigNamingDialogFragment fragment = ConfigNamingDialogFragment.newInstance(configText);
+ fragment.show(fragmentManager, null);
+ }
+ } catch (final IllegalArgumentException|IOException exception) {
+ onTunnelImportFinished(Collections.emptyList(), Collections.singletonList(exception));
+ }
+ }
+
private void importTunnel(@Nullable final Uri uri) {
final Activity activity = getActivity();
if (activity == null || uri == null)
@@ -172,6 +192,12 @@ public class TunnelListFragment extends BaseFragment {
if (resultCode == Activity.RESULT_OK && data != null)
importTunnel(data.getData());
return;
+ case IntentIntegrator.REQUEST_CODE:
+ final IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
+ if (result != null && result.getContents() != null) {
+ importTunnel(result.getContents());
+ }
+ return;
default:
super.onActivityResult(requestCode, resultCode, data);
}
@@ -217,6 +243,15 @@ public class TunnelListFragment extends BaseFragment {
binding.createMenu.collapse();
}
+ public void onRequestScanQRCode(@SuppressWarnings("unused") final View view) {
+ final IntentIntegrator intentIntegrator = IntentIntegrator.forSupportFragment(this);
+ intentIntegrator.setOrientationLocked(false);
+ intentIntegrator.initiateScan(Collections.singletonList(IntentIntegrator.QR_CODE));
+
+ if (binding != null)
+ binding.createMenu.collapse();
+ }
+
@Override
public void onPause() {
if (binding != null) {
diff --git a/app/src/main/java/com/wireguard/config/Config.java b/app/src/main/java/com/wireguard/config/Config.java
index 0599dec3..db8c2fc7 100644
--- a/app/src/main/java/com/wireguard/config/Config.java
+++ b/app/src/main/java/com/wireguard/config/Config.java
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@@ -32,6 +33,10 @@ public class Config {
private final Interface interfaceSection = new Interface();
private List peers = new ArrayList<>();
+ public static Config from(final String string) throws IOException {
+ return from(new BufferedReader(new StringReader(string)));
+ }
+
public static Config from(final InputStream stream) throws IOException {
return from(new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)));
}
diff --git a/app/src/main/res/drawable/ic_action_scan_qr_code_white.xml b/app/src/main/res/drawable/ic_action_scan_qr_code_white.xml
new file mode 100644
index 00000000..cdd83361
--- /dev/null
+++ b/app/src/main/res/drawable/ic_action_scan_qr_code_white.xml
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/config_naming_dialog_fragment.xml b/app/src/main/res/layout/config_naming_dialog_fragment.xml
new file mode 100644
index 00000000..aae78049
--- /dev/null
+++ b/app/src/main/res/layout/config_naming_dialog_fragment.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/tunnel_list_fragment.xml b/app/src/main/res/layout/tunnel_list_fragment.xml
index 48293133..2a7de392 100644
--- a/app/src/main/res/layout/tunnel_list_fragment.xml
+++ b/app/src/main/res/layout/tunnel_list_fragment.xml
@@ -38,6 +38,29 @@
app:layout="@{@layout/tunnel_list_item}"
app:configurationHandler="@{rowConfigurationHandler}" />
+
+
+
+
+
-
-
-
-
-
+ android:onClick="@{fragment::onRequestScanQRCode}"
+ app:srcCompat="@drawable/ic_action_scan_qr_code_white"
+ app:fabSize="mini"
+ app:fab_title="@string/scan_qr_code" />
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index bfaf71b6..1bb43fa0 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -30,6 +30,7 @@
Create WireGuard Tunnel
Create from scratch
Create from file or archive
+ Create Tunnel
Use dark theme
Currently using dark night theme
Currently using light day theme
@@ -71,6 +72,7 @@
Bring up previously-enabled tunnels on boot
Restore on boot
Save
+ Scan QR Code
- %d Excluded Application
- %d Excluded Applications
@@ -90,6 +92,7 @@
Unable to create tunnel: %s
Successfully created tunnel “%s”
Add a tunnel using the blue button
+ Tunnel Name
Unable to rename tunnel: %s
Successfully renamed tunnel to “%s”
WireGuard for Android v%s"