diff options
author | Eric Kuck <eric@bluelinelabs.com> | 2018-07-24 12:32:40 -0500 |
---|---|---|
committer | Jason A. Donenfeld <Jason@zx2c4.com> | 2018-07-25 02:05:11 +0200 |
commit | 8e0835e57084be1216e3ef1e71941295b2df909d (patch) | |
tree | 570f81ddc7b189e2a736f2ba669ab8ed553586d3 | |
parent | bb20c89cd5c75dceb591898fef733539092dba0b (diff) |
Added QR code scanner as tunnel import method
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
-rw-r--r-- | app/build.gradle | 2 | ||||
-rw-r--r-- | app/src/main/AndroidManifest.xml | 6 | ||||
-rw-r--r-- | app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java | 115 | ||||
-rw-r--r-- | app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java | 35 | ||||
-rw-r--r-- | app/src/main/java/com/wireguard/config/Config.java | 5 | ||||
-rw-r--r-- | app/src/main/res/drawable/ic_action_scan_qr_code_white.xml | 9 | ||||
-rw-r--r-- | app/src/main/res/layout/config_naming_dialog_fragment.xml | 25 | ||||
-rw-r--r-- | app/src/main/res/layout/tunnel_list_fragment.xml | 52 | ||||
-rw-r--r-- | app/src/main/res/values/strings.xml | 3 |
9 files changed, 231 insertions, 21 deletions
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"> + <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> @@ -50,6 +51,11 @@ android:label="@string/create_activity_title" android:parentActivityName=".activity.MainActivity" /> + <activity + android:name="com.journeyapps.barcodescanner.CaptureActivity" + android:screenOrientation="fullSensor" + tools:replace="screenOrientation" /> + <receiver android:name=".BootShutdownReceiver"> <intent-filter> <action android:name="android.intent.action.ACTION_SHUTDOWN" /> 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 <eric@bluelinelabs.com>. + * Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. 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<Peer> 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 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#ffffff" + android:pathData="M4,4H10V10H4V4M20,4V10H14V4H20M14,15H16V13H14V11H16V13H18V11H20V13H18V15H20V18H18V20H16V18H13V20H11V16H14V15M16,15V18H18V15H16M4,20V14H10V20H4M6,6V8H8V6H6M16,6V8H18V6H16M6,16V18H8V16H6M4,11H6V13H4V11M9,11H13V15H11V13H9V11M11,6H13V10H11V6M2,2V6H0V2A2,2 0 0,1 2,0H6V2H2M22,0A2,2 0 0,1 24,2V6H22V2H18V0H22M2,18V22H6V24H2A2,2 0 0,1 0,22V18H2M22,22V18H24V22A2,2 0 0,1 22,24H18V22H22Z" /> +</vector>
\ 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<layout xmlns:android="http://schemas.android.com/apk/res/android"> + + <FrameLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="16dp"> + + <android.support.design.widget.TextInputLayout + android:id="@+id/tunnel_name_text_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <EditText + android:id="@+id/tunnel_name_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/tunnel_name" /> + + </android.support.design.widget.TextInputLayout> + + </FrameLayout> + + +</layout> 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}" /> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:visibility="@{tunnels.size() == 0 ? android.view.View.VISIBLE : android.view.View.GONE}" + android:layout_gravity="center"> + <android.support.v7.widget.AppCompatImageView + android:id="@+id/logo_placeholder" + android:layout_width="140dp" + android:layout_height="140dp" + android:layout_gravity="center" + android:tint="@color/transparent_background_placeholder" + android:layout_marginTop="-70dp" + android:layout_marginBottom="20dp" + android:src="@mipmap/ic_launcher" /> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:textSize="20sp" + android:text="@string/tunnel_list_placeholder" /> + </LinearLayout> + <com.wireguard.android.widget.fab.FloatingActionsMenu android:id="@+id/create_menu" android:clipChildren="false" @@ -66,29 +89,16 @@ app:srcCompat="@drawable/ic_action_open_white" app:fabSize="mini" app:fab_title="@string/create_from_file" /> - </com.wireguard.android.widget.fab.FloatingActionsMenu> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:visibility="@{tunnels.size() == 0 ? android.view.View.VISIBLE : android.view.View.GONE}" - android:layout_gravity="center"> - <android.support.v7.widget.AppCompatImageView - android:id="@+id/logo_placeholder" - android:layout_width="140dp" - android:layout_height="140dp" - android:layout_gravity="center" - android:tint="@color/transparent_background_placeholder" - android:layout_marginTop="-70dp" - android:layout_marginBottom="20dp" - android:src="@mipmap/ic_launcher" /> - <TextView + <com.wireguard.android.widget.fab.LabeledFloatingActionButton + android:id="@+id/scan_qr_code" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="center" - android:textSize="20sp" - android:text="@string/tunnel_list_placeholder" /> - </LinearLayout> + android:onClick="@{fragment::onRequestScanQRCode}" + app:srcCompat="@drawable/ic_action_scan_qr_code_white" + app:fabSize="mini" + app:fab_title="@string/scan_qr_code" /> + </com.wireguard.android.widget.fab.FloatingActionsMenu> + </android.support.design.widget.CoordinatorLayout> </layout> 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 @@ <string name="create_activity_title">Create WireGuard Tunnel</string> <string name="create_empty">Create from scratch</string> <string name="create_from_file">Create from file or archive</string> + <string name="create_tunnel">Create Tunnel</string> <string name="dark_theme_title">Use dark theme</string> <string name="dark_theme_summary_on">Currently using dark night theme</string> <string name="dark_theme_summary_off">Currently using light day theme</string> @@ -71,6 +72,7 @@ <string name="restore_on_boot_summary">Bring up previously-enabled tunnels on boot</string> <string name="restore_on_boot_title">Restore on boot</string> <string name="save">Save</string> + <string name="scan_qr_code">Scan QR Code</string> <plurals name="set_excluded_applications"> <item quantity="one">%d Excluded Application</item> <item quantity="other">%d Excluded Applications</item> @@ -90,6 +92,7 @@ <string name="tunnel_create_error">Unable to create tunnel: %s</string> <string name="tunnel_create_success">Successfully created tunnel “%s”</string> <string name="tunnel_list_placeholder">Add a tunnel using the blue button</string> + <string name="tunnel_name">Tunnel Name</string> <string name="tunnel_rename_error">Unable to rename tunnel: %s</string> <string name="tunnel_rename_success">Successfully renamed tunnel to “%s”</string> <string name="version_title">WireGuard for Android v%s"</string> |