aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java')
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java479
1 files changed, 0 insertions, 479 deletions
diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java
deleted file mode 100644
index 59260500..00000000
--- a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java
+++ /dev/null
@@ -1,479 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.fragment;
-
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.content.ContentResolver;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.OpenableColumns;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import com.google.android.material.snackbar.Snackbar;
-import androidx.fragment.app.FragmentManager;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.view.ActionMode;
-import androidx.recyclerview.widget.RecyclerView;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.Menu;
-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;
-import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter;
-import com.wireguard.android.databinding.TunnelListFragmentBinding;
-import com.wireguard.android.databinding.TunnelListItemBinding;
-import com.wireguard.android.model.Tunnel;
-import com.wireguard.android.util.ErrorMessages;
-import com.wireguard.android.widget.MultiselectableRelativeLayout;
-import com.wireguard.android.widget.fab.FloatingActionsMenuRecyclerViewScrollListener;
-import com.wireguard.config.BadConfigException;
-import com.wireguard.config.Config;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-import java9.util.concurrent.CompletableFuture;
-import java9.util.stream.StreamSupport;
-
-/**
- * Fragment containing a list of known WireGuard tunnels. It allows creating and deleting tunnels.
- */
-
-public class TunnelListFragment extends BaseFragment {
- private static final int REQUEST_IMPORT = 1;
- private static final String TAG = "WireGuard/" + TunnelListFragment.class.getSimpleName();
-
- private final ActionModeListener actionModeListener = new ActionModeListener();
- @Nullable private ActionMode actionMode;
- @Nullable private TunnelListFragmentBinding binding;
-
- public boolean collapseActionMenu() {
- if (binding != null && binding.createMenu.isExpanded()) {
- binding.createMenu.collapse();
- return true;
- }
- return false;
- }
-
- private void importTunnel(@NonNull final String configText) {
- try {
- // Ensure the config text is parseable before proceeding…
- Config.parse(new ByteArrayInputStream(configText.getBytes(StandardCharsets.UTF_8)));
-
- // Config text is valid, now create the tunnel…
- final FragmentManager fragmentManager = getFragmentManager();
- if (fragmentManager != null)
- ConfigNamingDialogFragment.newInstance(configText).show(fragmentManager, null);
- } catch (final BadConfigException | IOException e) {
- onTunnelImportFinished(Collections.emptyList(), Collections.singletonList(e));
- }
- }
-
- private void importTunnel(@Nullable final Uri uri) {
- final Activity activity = getActivity();
- if (activity == null || uri == null)
- return;
- final ContentResolver contentResolver = activity.getContentResolver();
-
- final Collection<CompletableFuture<Tunnel>> futureTunnels = new ArrayList<>();
- final List<Throwable> throwables = new ArrayList<>();
- Application.getAsyncWorker().supplyAsync(() -> {
- final String[] columns = {OpenableColumns.DISPLAY_NAME};
- String name = null;
- try (Cursor cursor = contentResolver.query(uri, columns,
- null, null, null)) {
- if (cursor != null && cursor.moveToFirst() && !cursor.isNull(0))
- name = cursor.getString(0);
- }
- if (name == null)
- name = Uri.decode(uri.getLastPathSegment());
- int idx = name.lastIndexOf('/');
- if (idx >= 0) {
- if (idx >= name.length() - 1)
- throw new IllegalArgumentException(getResources().getString(R.string.illegal_filename_error, name));
- name = name.substring(idx + 1);
- }
- boolean isZip = name.toLowerCase(Locale.ENGLISH).endsWith(".zip");
- if (name.toLowerCase(Locale.ENGLISH).endsWith(".conf"))
- name = name.substring(0, name.length() - ".conf".length());
- else if (!isZip)
- throw new IllegalArgumentException(getResources().getString(R.string.bad_extension_error));
-
- if (isZip) {
- try (ZipInputStream zip = new ZipInputStream(contentResolver.openInputStream(uri));
- BufferedReader reader = new BufferedReader(new InputStreamReader(zip))) {
- ZipEntry entry;
- while ((entry = zip.getNextEntry()) != null) {
- if (entry.isDirectory())
- continue;
- name = entry.getName();
- idx = name.lastIndexOf('/');
- if (idx >= 0) {
- if (idx >= name.length() - 1)
- continue;
- name = name.substring(name.lastIndexOf('/') + 1);
- }
- if (name.toLowerCase(Locale.ENGLISH).endsWith(".conf"))
- name = name.substring(0, name.length() - ".conf".length());
- else
- continue;
- Config config = null;
- try {
- config = Config.parse(reader);
- } catch (Exception e) {
- throwables.add(e);
- }
- if (config != null)
- futureTunnels.add(Application.getTunnelManager().create(name, config).toCompletableFuture());
- }
- }
- } else {
- futureTunnels.add(Application.getTunnelManager().create(name,
- Config.parse(contentResolver.openInputStream(uri))).toCompletableFuture());
- }
-
- if (futureTunnels.isEmpty()) {
- if (throwables.size() == 1)
- throw throwables.get(0);
- else if (throwables.isEmpty())
- throw new IllegalArgumentException(getResources().getString(R.string.no_configs_error));
- }
-
- return CompletableFuture.allOf(futureTunnels.toArray(new CompletableFuture[futureTunnels.size()]));
- }).whenComplete((future, exception) -> {
- if (exception != null) {
- onTunnelImportFinished(Collections.emptyList(), Collections.singletonList(exception));
- } else {
- future.whenComplete((ignored1, ignored2) -> {
- final List<Tunnel> tunnels = new ArrayList<>(futureTunnels.size());
- for (final CompletableFuture<Tunnel> futureTunnel : futureTunnels) {
- Tunnel tunnel = null;
- try {
- tunnel = futureTunnel.getNow(null);
- } catch (final Exception e) {
- throwables.add(e);
- }
- if (tunnel != null)
- tunnels.add(tunnel);
- }
- onTunnelImportFinished(tunnels, throwables);
- });
- }
- });
- }
-
- @Override
- public void onActivityCreated(@Nullable final Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- if (savedInstanceState != null) {
- final Collection<Integer> checkedItems = savedInstanceState.getIntegerArrayList("CHECKED_ITEMS");
- if (checkedItems != null) {
- for (final Integer i : checkedItems)
- actionModeListener.setItemChecked(i, true);
- }
- }
- }
-
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
- switch (requestCode) {
- case REQUEST_IMPORT:
- 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);
- }
- }
-
- @SuppressWarnings("deprecation")
- @SuppressLint("ClickableViewAccessibility")
- @Override
- public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container,
- @Nullable final Bundle savedInstanceState) {
- super.onCreateView(inflater, container, savedInstanceState);
- binding = TunnelListFragmentBinding.inflate(inflater, container, false);
-
- binding.tunnelList.setOnTouchListener((view, motionEvent) -> {
- if (binding != null) {
- binding.createMenu.collapse();
- }
- return false;
- });
- binding.tunnelList.setOnScrollListener(new FloatingActionsMenuRecyclerViewScrollListener(binding.createMenu));
- binding.executePendingBindings();
- return binding.getRoot();
- }
-
- @Override
- public void onDestroyView() {
- binding = null;
- super.onDestroyView();
- }
-
- @Override
- public void onPause() {
- if (binding != null) {
- binding.createMenu.collapse();
- }
- super.onPause();
- }
-
- public void onRequestCreateConfig(@SuppressWarnings("unused") final View view) {
- startActivity(new Intent(getActivity(), TunnelCreatorActivity.class));
- if (binding != null)
- binding.createMenu.collapse();
- }
-
- public void onRequestImportConfig(@SuppressWarnings("unused") final View view) {
- final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("*/*");
- startActivityForResult(intent, REQUEST_IMPORT);
- if (binding != null)
- binding.createMenu.collapse();
- }
-
- public void onRequestScanQRCode(@SuppressWarnings("unused") final View view) {
- final IntentIntegrator intentIntegrator = IntentIntegrator.forSupportFragment(this);
- intentIntegrator.setOrientationLocked(false);
- intentIntegrator.setBeepEnabled(false);
- intentIntegrator.setPrompt(getString(R.string.qr_code_hint));
- intentIntegrator.initiateScan(Collections.singletonList(IntentIntegrator.QR_CODE));
-
- if (binding != null)
- binding.createMenu.collapse();
- }
-
- @Override
- public void onSaveInstanceState(final Bundle outState) {
- super.onSaveInstanceState(outState);
-
- outState.putIntegerArrayList("CHECKED_ITEMS", actionModeListener.getCheckedItems());
- }
-
- @Override
- public void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, @Nullable final Tunnel newTunnel) {
- if (binding == null)
- return;
- Application.getTunnelManager().getTunnels().thenAccept(tunnels -> {
- if (newTunnel != null)
- viewForTunnel(newTunnel, tunnels).setSingleSelected(true);
- if (oldTunnel != null)
- viewForTunnel(oldTunnel, tunnels).setSingleSelected(false);
- });
- }
-
- private void onTunnelDeletionFinished(final Integer count, @Nullable final Throwable throwable) {
- final String message;
- if (throwable == null) {
- message = getResources().getQuantityString(R.plurals.delete_success, count, count);
- } else {
- final String error = ErrorMessages.get(throwable);
- message = getResources().getQuantityString(R.plurals.delete_error, count, count, error);
- Log.e(TAG, message, throwable);
- }
- if (binding != null) {
- Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show();
- }
- }
-
- private void onTunnelImportFinished(final List<Tunnel> tunnels, final Collection<Throwable> throwables) {
- String message = null;
-
- for (final Throwable throwable : throwables) {
- final String error = ErrorMessages.get(throwable);
- message = getString(R.string.import_error, error);
- Log.e(TAG, message, throwable);
- }
-
- if (tunnels.size() == 1 && throwables.isEmpty())
- message = getString(R.string.import_success, tunnels.get(0).getName());
- else if (tunnels.isEmpty() && throwables.size() == 1)
- /* Use the exception message from above. */ ;
- else if (throwables.isEmpty())
- message = getResources().getQuantityString(R.plurals.import_total_success,
- tunnels.size(), tunnels.size());
- else if (!throwables.isEmpty())
- message = getResources().getQuantityString(R.plurals.import_partial_success,
- tunnels.size() + throwables.size(),
- tunnels.size(), tunnels.size() + throwables.size());
-
- if (binding != null)
- Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show();
- }
-
- @Override
- public void onViewStateRestored(@Nullable final Bundle savedInstanceState) {
- super.onViewStateRestored(savedInstanceState);
-
- if (binding == null) {
- return;
- }
-
- binding.setFragment(this);
- Application.getTunnelManager().getTunnels().thenAccept(binding::setTunnels);
- binding.setRowConfigurationHandler((ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler<TunnelListItemBinding, Tunnel>) (binding, tunnel, position) -> {
- binding.setFragment(this);
- binding.getRoot().setOnClickListener(clicked -> {
- if (actionMode == null) {
- setSelectedTunnel(tunnel);
- } else {
- actionModeListener.toggleItemChecked(position);
- }
- });
- binding.getRoot().setOnLongClickListener(clicked -> {
- actionModeListener.toggleItemChecked(position);
- return true;
- });
-
- if (actionMode != null)
- ((MultiselectableRelativeLayout) binding.getRoot()).setMultiSelected(actionModeListener.checkedItems.contains(position));
- else
- ((MultiselectableRelativeLayout) binding.getRoot()).setSingleSelected(getSelectedTunnel() == tunnel);
- });
- }
-
- private MultiselectableRelativeLayout viewForTunnel(final Tunnel tunnel, final List tunnels) {
- return (MultiselectableRelativeLayout) binding.tunnelList.findViewHolderForAdapterPosition(tunnels.indexOf(tunnel)).itemView;
- }
-
- private final class ActionModeListener implements ActionMode.Callback {
- private final Collection<Integer> checkedItems = new HashSet<>();
-
- @Nullable private Resources resources;
-
- public ArrayList<Integer> getCheckedItems() {
- return new ArrayList<>(checkedItems);
- }
-
- @Override
- public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_action_delete:
- final Iterable<Integer> copyCheckedItems = new HashSet<>(checkedItems);
- Application.getTunnelManager().getTunnels().thenAccept(tunnels -> {
- final Collection<Tunnel> tunnelsToDelete = new ArrayList<>();
- for (final Integer position : copyCheckedItems)
- tunnelsToDelete.add(tunnels.get(position));
-
- final CompletableFuture[] futures = StreamSupport.stream(tunnelsToDelete)
- .map(Tunnel::delete)
- .toArray(CompletableFuture[]::new);
- CompletableFuture.allOf(futures)
- .thenApply(x -> futures.length)
- .whenComplete(TunnelListFragment.this::onTunnelDeletionFinished);
-
- });
- checkedItems.clear();
- mode.finish();
- return true;
- case R.id.menu_action_select_all:
- Application.getTunnelManager().getTunnels().thenAccept(tunnels -> {
- for (int i = 0; i < tunnels.size(); ++i) {
- setItemChecked(i, true);
- }
- });
- return true;
- default:
- return false;
- }
- }
-
- @Override
- public boolean onCreateActionMode(final ActionMode mode, final Menu menu) {
- actionMode = mode;
- if (getActivity() != null) {
- resources = getActivity().getResources();
- }
- mode.getMenuInflater().inflate(R.menu.tunnel_list_action_mode, menu);
- binding.tunnelList.getAdapter().notifyDataSetChanged();
- return true;
- }
-
- @Override
- public void onDestroyActionMode(final ActionMode mode) {
- actionMode = null;
- resources = null;
- checkedItems.clear();
- binding.tunnelList.getAdapter().notifyDataSetChanged();
- }
-
- @Override
- public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) {
- updateTitle(mode);
- return false;
- }
-
- void setItemChecked(final int position, final boolean checked) {
- if (checked) {
- checkedItems.add(position);
- } else {
- checkedItems.remove(position);
- }
-
- final RecyclerView.Adapter adapter = binding == null ? null : binding.tunnelList.getAdapter();
-
- if (actionMode == null && !checkedItems.isEmpty() && getActivity() != null) {
- ((AppCompatActivity) getActivity()).startSupportActionMode(this);
- } else if (actionMode != null && checkedItems.isEmpty()) {
- actionMode.finish();
- }
-
- if (adapter != null)
- adapter.notifyItemChanged(position);
-
- updateTitle(actionMode);
- }
-
- void toggleItemChecked(final int position) {
- setItemChecked(position, !checkedItems.contains(position));
- }
-
- private void updateTitle(@Nullable final ActionMode mode) {
- if (mode == null) {
- return;
- }
-
- final int count = checkedItems.size();
- if (count == 0) {
- mode.setTitle("");
- } else {
- mode.setTitle(resources.getQuantityString(R.plurals.delete_title, count, count));
- }
- }
- }
-
-}