summaryrefslogtreecommitdiffhomepage
path: root/app/src/main/java/com/wireguard/android/fragment
diff options
context:
space:
mode:
authorHarsh Shandilya <me@msfjarvis.dev>2020-03-09 19:06:11 +0530
committerHarsh Shandilya <me@msfjarvis.dev>2020-03-09 19:24:27 +0530
commit7d48bef70a56d4370856eedab619b1f83ac3d0d0 (patch)
tree76fd859578e499cd3a8fd2f402652530ea36a72d /app/src/main/java/com/wireguard/android/fragment
parent6bc3e257f80a273d35d07099bd4ed99eb45163bf (diff)
Rename app module to ui
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
Diffstat (limited to 'app/src/main/java/com/wireguard/android/fragment')
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/AddTunnelsSheet.kt106
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java140
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/BaseFragment.java126
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java118
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java162
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java264
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java449
7 files changed, 0 insertions, 1365 deletions
diff --git a/app/src/main/java/com/wireguard/android/fragment/AddTunnelsSheet.kt b/app/src/main/java/com/wireguard/android/fragment/AddTunnelsSheet.kt
deleted file mode 100644
index 3df141be..00000000
--- a/app/src/main/java/com/wireguard/android/fragment/AddTunnelsSheet.kt
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright © 2020 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-package com.wireguard.android.fragment
-
-import android.content.Intent
-import android.graphics.drawable.GradientDrawable
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewTreeObserver
-import android.widget.FrameLayout
-import androidx.fragment.app.Fragment
-import com.google.android.material.bottomsheet.BottomSheetBehavior
-import com.google.android.material.bottomsheet.BottomSheetDialog
-import com.google.android.material.bottomsheet.BottomSheetDialogFragment
-import com.google.zxing.integration.android.IntentIntegrator
-import com.wireguard.android.R
-import com.wireguard.android.activity.TunnelCreatorActivity
-import com.wireguard.android.util.resolveAttribute
-
-class AddTunnelsSheet : BottomSheetDialogFragment() {
-
- private lateinit var behavior: BottomSheetBehavior<FrameLayout>
- private val bottomSheetCallback = object : BottomSheetBehavior.BottomSheetCallback() {
- override fun onSlide(bottomSheet: View, slideOffset: Float) {
- }
-
- override fun onStateChanged(bottomSheet: View, newState: Int) {
- if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
- dismiss()
- }
- }
- }
-
- override fun getTheme(): Int {
- return R.style.BottomSheetDialogTheme
- }
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- if (savedInstanceState != null) dismiss()
- return inflater.inflate(R.layout.add_tunnels_bottom_sheet, container, false)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- view.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
- override fun onGlobalLayout() {
- view.viewTreeObserver.removeOnGlobalLayoutListener(this)
- val dialog = dialog as BottomSheetDialog? ?: return
- behavior = dialog.behavior
- behavior.state = BottomSheetBehavior.STATE_EXPANDED
- behavior.peekHeight = 0
- behavior.addBottomSheetCallback(bottomSheetCallback)
- dialog.findViewById<View>(R.id.create_empty)?.setOnClickListener {
- dismiss()
- onRequestCreateConfig()
- }
- dialog.findViewById<View>(R.id.create_from_file)?.setOnClickListener {
- dismiss()
- onRequestImportConfig()
- }
- dialog.findViewById<View>(R.id.create_from_qrcode)?.setOnClickListener {
- dismiss()
- onRequestScanQRCode()
- }
- }
- })
- val gradientDrawable = GradientDrawable().apply {
- setColor(requireContext().resolveAttribute(R.attr.colorBackground))
- }
- view.background = gradientDrawable
- }
-
- override fun dismiss() {
- super.dismiss()
- behavior.removeBottomSheetCallback(bottomSheetCallback)
- }
-
- private fun requireTargetFragment(): Fragment {
- return requireNotNull(targetFragment) { "A target fragment should always be set" }
- }
-
- private fun onRequestCreateConfig() {
- startActivity(Intent(activity, TunnelCreatorActivity::class.java))
- }
-
- private fun onRequestImportConfig() {
- val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
- addCategory(Intent.CATEGORY_OPENABLE)
- type = "*/*"
- }
- requireTargetFragment().startActivityForResult(intent, TunnelListFragment.REQUEST_IMPORT)
- }
-
- private fun onRequestScanQRCode() {
- val integrator = IntentIntegrator.forSupportFragment(requireTargetFragment()).apply {
- setOrientationLocked(false)
- setBeepEnabled(false)
- setPrompt(getString(R.string.qr_code_hint))
- }
- integrator.initiateScan(listOf(IntentIntegrator.QR_CODE))
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java b/app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java
deleted file mode 100644
index 43178665..00000000
--- a/app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. 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.DialogInterface;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Bundle;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.Fragment;
-import androidx.appcompat.app.AlertDialog;
-import android.widget.Toast;
-
-import com.wireguard.android.Application;
-import com.wireguard.android.R;
-import com.wireguard.android.databinding.AppListDialogFragmentBinding;
-import com.wireguard.android.model.ApplicationData;
-import com.wireguard.android.util.ErrorMessages;
-import com.wireguard.android.util.ObservableKeyedArrayList;
-import com.wireguard.android.util.ObservableKeyedList;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import java9.util.Comparators;
-import java9.util.stream.Collectors;
-import java9.util.stream.StreamSupport;
-
-public class AppListDialogFragment extends DialogFragment {
-
- private static final String KEY_EXCLUDED_APPS = "excludedApps";
- private final ObservableKeyedList<String, ApplicationData> appData = new ObservableKeyedArrayList<>();
- private List<String> currentlyExcludedApps = Collections.emptyList();
-
- public static <T extends Fragment & AppExclusionListener>
- AppListDialogFragment newInstance(final ArrayList<String> excludedApps, final T target) {
- final Bundle extras = new Bundle();
- extras.putStringArrayList(KEY_EXCLUDED_APPS, excludedApps);
- final AppListDialogFragment fragment = new AppListDialogFragment();
- fragment.setTargetFragment(target, 0);
- fragment.setArguments(extras);
- return fragment;
- }
-
- private void loadData() {
- final Activity activity = getActivity();
- if (activity == null) {
- return;
- }
-
- final PackageManager pm = activity.getPackageManager();
- Application.getAsyncWorker().supplyAsync(() -> {
- final Intent launcherIntent = new Intent(Intent.ACTION_MAIN, null);
- launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(launcherIntent, 0);
-
- final List<ApplicationData> applicationData = new ArrayList<>();
- for (ResolveInfo resolveInfo : resolveInfos) {
- String packageName = resolveInfo.activityInfo.packageName;
- applicationData.add(new ApplicationData(resolveInfo.loadIcon(pm), resolveInfo.loadLabel(pm).toString(), packageName, currentlyExcludedApps.contains(packageName)));
- }
-
- Collections.sort(applicationData, Comparators.comparing(ApplicationData::getName, String.CASE_INSENSITIVE_ORDER));
- return applicationData;
- }).whenComplete(((data, throwable) -> {
- if (data != null) {
- appData.clear();
- appData.addAll(data);
- } else {
- final String error = ErrorMessages.get(throwable);
- final String message = activity.getString(R.string.error_fetching_apps, error);
- Toast.makeText(activity, message, Toast.LENGTH_LONG).show();
- dismissAllowingStateLoss();
- }
- }));
- }
-
- @Override
- public void onCreate(@Nullable final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- final List<String> excludedApps = requireArguments().getStringArrayList(KEY_EXCLUDED_APPS);
- currentlyExcludedApps = (excludedApps != null) ? excludedApps : Collections.emptyList();
- }
-
- @Override
- public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
- final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireActivity());
- alertDialogBuilder.setTitle(R.string.excluded_applications);
-
- final AppListDialogFragmentBinding binding = AppListDialogFragmentBinding.inflate(requireActivity().getLayoutInflater(), null, false);
- binding.executePendingBindings();
- alertDialogBuilder.setView(binding.getRoot());
-
- alertDialogBuilder.setPositiveButton(R.string.set_exclusions, (dialog, which) -> setExclusionsAndDismiss());
- alertDialogBuilder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
- alertDialogBuilder.setNeutralButton(R.string.toggle_all, (dialog, which) -> {
- });
-
- binding.setFragment(this);
- binding.setAppData(appData);
-
- loadData();
-
- final AlertDialog dialog = alertDialogBuilder.create();
- dialog.setOnShowListener(d -> dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(view -> {
- final List<ApplicationData> selectedItems = StreamSupport.stream(appData)
- .filter(ApplicationData::isExcludedFromTunnel)
- .collect(Collectors.toList());
- final boolean excludeAll = selectedItems.isEmpty();
- for (final ApplicationData app : appData)
- app.setExcludedFromTunnel(excludeAll);
- }));
- return dialog;
- }
-
- private void setExclusionsAndDismiss() {
- final List<String> excludedApps = new ArrayList<>();
- for (final ApplicationData data : appData) {
- if (data.isExcludedFromTunnel()) {
- excludedApps.add(data.getPackageName());
- }
- }
-
- ((AppExclusionListener) getTargetFragment()).onExcludedAppsSelected(excludedApps);
- dismiss();
- }
-
- public interface AppExclusionListener {
- void onExcludedAppsSelected(List<String> excludedApps);
- }
-
-}
diff --git a/app/src/main/java/com/wireguard/android/fragment/BaseFragment.java b/app/src/main/java/com/wireguard/android/fragment/BaseFragment.java
deleted file mode 100644
index 23bf44e7..00000000
--- a/app/src/main/java/com/wireguard/android/fragment/BaseFragment.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.fragment;
-
-import android.content.Context;
-import android.content.Intent;
-import androidx.databinding.DataBindingUtil;
-import androidx.databinding.ViewDataBinding;
-import androidx.annotation.Nullable;
-import com.google.android.material.snackbar.Snackbar;
-import androidx.fragment.app.Fragment;
-import android.util.Log;
-import android.view.View;
-import android.widget.Toast;
-
-import com.wireguard.android.Application;
-import com.wireguard.android.R;
-import com.wireguard.android.activity.BaseActivity;
-import com.wireguard.android.activity.BaseActivity.OnSelectedTunnelChangedListener;
-import com.wireguard.android.backend.GoBackend;
-import com.wireguard.android.databinding.TunnelDetailFragmentBinding;
-import com.wireguard.android.databinding.TunnelListItemBinding;
-import com.wireguard.android.model.ObservableTunnel;
-import com.wireguard.android.backend.Tunnel.State;
-import com.wireguard.android.util.ErrorMessages;
-
-/**
- * Base class for fragments that need to know the currently-selected tunnel. Only does anything when
- * attached to a {@code BaseActivity}.
- */
-
-public abstract class BaseFragment extends Fragment implements OnSelectedTunnelChangedListener {
- private static final int REQUEST_CODE_VPN_PERMISSION = 23491;
- private static final String TAG = "WireGuard/" + BaseFragment.class.getSimpleName();
- @Nullable private BaseActivity activity;
- @Nullable private ObservableTunnel pendingTunnel;
- @Nullable private Boolean pendingTunnelUp;
-
- @Nullable
- protected ObservableTunnel getSelectedTunnel() {
- return activity != null ? activity.getSelectedTunnel() : null;
- }
-
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
-
- if (requestCode == REQUEST_CODE_VPN_PERMISSION) {
- if (pendingTunnel != null && pendingTunnelUp != null)
- setTunnelStateWithPermissionsResult(pendingTunnel, pendingTunnelUp);
- pendingTunnel = null;
- pendingTunnelUp = null;
- }
- }
-
- @Override
- public void onAttach(final Context context) {
- super.onAttach(context);
- if (context instanceof BaseActivity) {
- activity = (BaseActivity) context;
- activity.addOnSelectedTunnelChangedListener(this);
- } else {
- activity = null;
- }
- }
-
- @Override
- public void onDetach() {
- if (activity != null)
- activity.removeOnSelectedTunnelChangedListener(this);
- activity = null;
- super.onDetach();
- }
-
- protected void setSelectedTunnel(@Nullable final ObservableTunnel tunnel) {
- if (activity != null)
- activity.setSelectedTunnel(tunnel);
- }
-
- public void setTunnelState(final View view, final boolean checked) {
- final ViewDataBinding binding = DataBindingUtil.findBinding(view);
- final ObservableTunnel tunnel;
- if (binding instanceof TunnelDetailFragmentBinding)
- tunnel = ((TunnelDetailFragmentBinding) binding).getTunnel();
- else if (binding instanceof TunnelListItemBinding)
- tunnel = ((TunnelListItemBinding) binding).getItem();
- else
- return;
- if (tunnel == null)
- return;
-
- Application.getBackendAsync().thenAccept(backend -> {
- if (backend instanceof GoBackend) {
- final Intent intent = GoBackend.VpnService.prepare(view.getContext());
- if (intent != null) {
- pendingTunnel = tunnel;
- pendingTunnelUp = checked;
- startActivityForResult(intent, REQUEST_CODE_VPN_PERMISSION);
- return;
- }
- }
-
- setTunnelStateWithPermissionsResult(tunnel, checked);
- });
- }
-
- private void setTunnelStateWithPermissionsResult(final ObservableTunnel tunnel, final boolean checked) {
- tunnel.setState(State.of(checked)).whenComplete((state, throwable) -> {
- if (throwable == null)
- return;
- final String error = ErrorMessages.get(throwable);
- final int messageResId = checked ? R.string.error_up : R.string.error_down;
- final String message = requireContext().getString(messageResId, error);
- final View view = getView();
- if (view != null)
- Snackbar.make(view, message, Snackbar.LENGTH_LONG).show();
- else
- Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show();
- Log.e(TAG, message, throwable);
- });
- }
-
-}
diff --git a/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java b/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java
deleted file mode 100644
index effa0593..00000000
--- a/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. 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 androidx.annotation.Nullable;
-import androidx.fragment.app.DialogFragment;
-import androidx.appcompat.app.AlertDialog;
-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.BadConfigException;
-import com.wireguard.config.Config;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Objects;
-
-public class ConfigNamingDialogFragment extends DialogFragment {
- private static final String KEY_CONFIG_TEXT = "config_text";
-
- @Nullable private ConfigNamingDialogFragmentBinding binding;
- @Nullable private Config config;
- @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;
- }
-
- 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());
- }
- });
- }
- }
-
- @Override
- public void dismiss() {
- setKeyboardVisible(false);
- super.dismiss();
- }
-
- @Override
- public void onCreate(@Nullable final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- final Bundle arguments = getArguments();
- final String configText = arguments.getString(KEY_CONFIG_TEXT);
- final byte[] configBytes = configText.getBytes(StandardCharsets.UTF_8);
- try {
- config = Config.parse(new ByteArrayInputStream(configBytes));
- } catch (final BadConfigException | IOException e) {
- throw new IllegalArgumentException("Invalid config passed to " + getClass().getSimpleName(), e);
- }
- }
-
- @Override
- public Dialog onCreateDialog(final Bundle savedInstanceState) {
- final Activity activity = requireActivity();
-
- imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
-
- final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(activity);
- alertDialogBuilder.setTitle(R.string.import_from_qr_code);
-
- binding = ConfigNamingDialogFragmentBinding.inflate(activity.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 onResume() {
- super.onResume();
-
- final AlertDialog dialog = (AlertDialog) getDialog();
- if (dialog != null) {
- dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> createTunnelAndDismiss());
-
- setKeyboardVisible(true);
- }
- }
-
- 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/TunnelDetailFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java
deleted file mode 100644
index 8d90fa7e..00000000
--- a/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.fragment;
-
-import android.os.Bundle;
-import androidx.annotation.Nullable;
-import androidx.databinding.DataBindingUtil;
-
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.wireguard.android.R;
-import com.wireguard.android.databinding.TunnelDetailFragmentBinding;
-import com.wireguard.android.databinding.TunnelDetailPeerBinding;
-import com.wireguard.android.model.ObservableTunnel;
-import com.wireguard.android.backend.Tunnel.State;
-import com.wireguard.android.ui.EdgeToEdge;
-import com.wireguard.crypto.Key;
-
-import java.util.Timer;
-import java.util.TimerTask;
-
-/**
- * Fragment that shows details about a specific tunnel.
- */
-
-public class TunnelDetailFragment extends BaseFragment {
- @Nullable private TunnelDetailFragmentBinding binding;
- @Nullable private Timer timer;
- @Nullable private State lastState = State.TOGGLE;
-
- @Override
- public void onCreate(@Nullable final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setHasOptionsMenu(true);
- }
-
- @Override
- public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- inflater.inflate(R.menu.tunnel_detail, menu);
- }
-
- @Override
- public void onStop() {
- super.onStop();
- if (timer != null) {
- timer.cancel();
- timer = null;
- }
- }
-
- @Override
- public void onResume() {
- super.onResume();
- timer = new Timer();
- timer.scheduleAtFixedRate(new TimerTask() {
- @Override
- public void run() {
- updateStats();
- }
- }, 0, 1000);
- }
-
- @Override
- public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
- @Nullable final Bundle savedInstanceState) {
- super.onCreateView(inflater, container, savedInstanceState);
- binding = TunnelDetailFragmentBinding.inflate(inflater, container, false);
- binding.executePendingBindings();
- EdgeToEdge.setUpRoot((ViewGroup) binding.getRoot());
- EdgeToEdge.setUpScrollingContent((ViewGroup) binding.getRoot(), null);
- return binding.getRoot();
- }
-
- @Override
- public void onDestroyView() {
- binding = null;
- super.onDestroyView();
- }
-
- @Override
- public void onSelectedTunnelChanged(@Nullable final ObservableTunnel oldTunnel, @Nullable final ObservableTunnel newTunnel) {
- if (binding == null)
- return;
- binding.setTunnel(newTunnel);
- if (newTunnel == null)
- binding.setConfig(null);
- else
- newTunnel.getConfigAsync().thenAccept(binding::setConfig);
- lastState = State.TOGGLE;
- updateStats();
- }
-
- @Override
- public void onViewStateRestored(@Nullable final Bundle savedInstanceState) {
- if (binding == null) {
- return;
- }
-
- binding.setFragment(this);
- onSelectedTunnelChanged(null, getSelectedTunnel());
- super.onViewStateRestored(savedInstanceState);
- }
-
- private String formatBytes(final long bytes) {
- if (bytes < 1024)
- return requireContext().getString(R.string.transfer_bytes, bytes);
- else if (bytes < 1024*1024)
- return requireContext().getString(R.string.transfer_kibibytes, bytes/1024.0);
- else if (bytes < 1024*1024*1024)
- return requireContext().getString(R.string.transfer_mibibytes, bytes/(1024.0*1024.0));
- else if (bytes < 1024*1024*1024*1024)
- return requireContext().getString(R.string.transfer_gibibytes, bytes/(1024.0*1024.0*1024.0));
- return requireContext().getString(R.string.transfer_tibibytes, bytes/(1024.0*1024.0*1024.0)/1024.0);
- }
-
- private void updateStats() {
- if (binding == null || !isResumed())
- return;
- final ObservableTunnel tunnel = binding.getTunnel();
- if (tunnel == null)
- return;
- final State state = tunnel.getState();
- if (state != State.UP && lastState == state)
- return;
- lastState = state;
- tunnel.getStatisticsAsync().whenComplete((statistics, throwable) -> {
- if (throwable != null) {
- for (int i = 0; i < binding.peersLayout.getChildCount(); ++i) {
- final TunnelDetailPeerBinding peer = DataBindingUtil.getBinding(binding.peersLayout.getChildAt(i));
- if (peer == null)
- continue;
- peer.transferLabel.setVisibility(View.GONE);
- peer.transferText.setVisibility(View.GONE);
- }
- return;
- }
- for (int i = 0; i < binding.peersLayout.getChildCount(); ++i) {
- final TunnelDetailPeerBinding peer = DataBindingUtil.getBinding(binding.peersLayout.getChildAt(i));
- if (peer == null)
- continue;
- final Key publicKey = peer.getItem().getPublicKey();
- final long rx = statistics.peerRx(publicKey);
- final long tx = statistics.peerTx(publicKey);
- if (rx == 0 && tx == 0) {
- peer.transferLabel.setVisibility(View.GONE);
- peer.transferText.setVisibility(View.GONE);
- continue;
- }
- peer.transferText.setText(requireContext().getString(R.string.transfer_rx_tx, formatBytes(rx), formatBytes(tx)));
- peer.transferLabel.setVisibility(View.VISIBLE);
- peer.transferText.setVisibility(View.VISIBLE);
- }
- });
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java
deleted file mode 100644
index 92aeb52a..00000000
--- a/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.fragment;
-
-import android.app.Activity;
-import android.content.Context;
-import androidx.databinding.ObservableList;
-import android.os.Bundle;
-import androidx.annotation.Nullable;
-import com.google.android.material.snackbar.Snackbar;
-
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.Toast;
-
-import com.wireguard.android.Application;
-import com.wireguard.android.R;
-import com.wireguard.android.databinding.TunnelEditorFragmentBinding;
-import com.wireguard.android.fragment.AppListDialogFragment.AppExclusionListener;
-import com.wireguard.android.model.ObservableTunnel;
-import com.wireguard.android.model.TunnelManager;
-import com.wireguard.android.ui.EdgeToEdge;
-import com.wireguard.android.util.ErrorMessages;
-import com.wireguard.android.viewmodel.ConfigProxy;
-import com.wireguard.config.Config;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Fragment for editing a WireGuard configuration.
- */
-
-public class TunnelEditorFragment extends BaseFragment implements AppExclusionListener {
- private static final String KEY_LOCAL_CONFIG = "local_config";
- private static final String KEY_ORIGINAL_NAME = "original_name";
- private static final String TAG = "WireGuard/" + TunnelEditorFragment.class.getSimpleName();
-
- @Nullable private TunnelEditorFragmentBinding binding;
- @Nullable private ObservableTunnel tunnel;
-
- private void onConfigLoaded(final Config config) {
- if (binding != null) {
- binding.setConfig(new ConfigProxy(config));
- }
- }
-
- private void onConfigSaved(final ObservableTunnel savedTunnel,
- @Nullable final Throwable throwable) {
- final String message;
- if (throwable == null) {
- message = getString(R.string.config_save_success, savedTunnel.getName());
- Log.d(TAG, message);
- Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show();
- onFinished();
- } else {
- final String error = ErrorMessages.get(throwable);
- message = getString(R.string.config_save_error, savedTunnel.getName(), error);
- Log.e(TAG, message, throwable);
- if (binding != null) {
- Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show();
- }
- }
- }
-
- @Override
- public void onCreate(@Nullable final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setHasOptionsMenu(true);
- }
-
- @Override
- public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- inflater.inflate(R.menu.config_editor, menu);
- }
-
- @Override
- public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
- @Nullable final Bundle savedInstanceState) {
- super.onCreateView(inflater, container, savedInstanceState);
- binding = TunnelEditorFragmentBinding.inflate(inflater, container, false);
- binding.executePendingBindings();
- EdgeToEdge.setUpRoot((ViewGroup) binding.getRoot());
- EdgeToEdge.setUpScrollingContent(binding.mainContainer, null);
- return binding.getRoot();
- }
-
- @Override
- public void onDestroyView() {
- binding = null;
- super.onDestroyView();
- }
-
- @Override
- public void onExcludedAppsSelected(final List<String> excludedApps) {
- Objects.requireNonNull(binding, "Tried to set excluded apps while no view was loaded");
- final ObservableList<String> excludedApplications =
- binding.getConfig().getInterface().getExcludedApplications();
- excludedApplications.clear();
- excludedApplications.addAll(excludedApps);
- }
-
- private void onFinished() {
- // Hide the keyboard; it rarely goes away on its own.
- final Activity activity = getActivity();
- if (activity == null) return;
- final View focusedView = activity.getCurrentFocus();
- if (focusedView != null) {
- final Object service = activity.getSystemService(Context.INPUT_METHOD_SERVICE);
- final InputMethodManager inputManager = (InputMethodManager) service;
- if (inputManager != null)
- inputManager.hideSoftInputFromWindow(focusedView.getWindowToken(),
- InputMethodManager.HIDE_NOT_ALWAYS);
- }
- // Tell the activity to finish itself or go back to the detail view.
- getActivity().runOnUiThread(() -> {
- // TODO(smaeul): Remove this hack when fixing the Config ViewModel
- // The selected tunnel has to actually change, but we have to remember this one.
- final ObservableTunnel savedTunnel = tunnel;
- if (savedTunnel == getSelectedTunnel())
- setSelectedTunnel(null);
- setSelectedTunnel(savedTunnel);
- });
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_action_save:
- if (binding == null)
- return false;
- final Config newConfig;
- try {
- newConfig = binding.getConfig().resolve();
- } catch (final Exception e) {
- final String error = ErrorMessages.get(e);
- final String tunnelName = tunnel == null ? binding.getName() : tunnel.getName();
- final String message = getString(R.string.config_save_error, tunnelName, error);
- Log.e(TAG, message, e);
- Snackbar.make(binding.mainContainer, error, Snackbar.LENGTH_LONG).show();
- return false;
- }
- if (tunnel == null) {
- Log.d(TAG, "Attempting to create new tunnel " + binding.getName());
- final TunnelManager manager = Application.getTunnelManager();
- manager.create(binding.getName(), newConfig)
- .whenComplete(this::onTunnelCreated);
- } else if (!tunnel.getName().equals(binding.getName())) {
- Log.d(TAG, "Attempting to rename tunnel to " + binding.getName());
- tunnel.setName(binding.getName())
- .whenComplete((a, b) -> onTunnelRenamed(tunnel, newConfig, b));
- } else {
- Log.d(TAG, "Attempting to save config of " + tunnel.getName());
- tunnel.setConfig(newConfig)
- .whenComplete((a, b) -> onConfigSaved(tunnel, b));
- }
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- public void onRequestSetExcludedApplications(@SuppressWarnings("unused") final View view) {
- if (binding != null) {
- final ArrayList<String> excludedApps = new ArrayList<>(binding.getConfig().getInterface().getExcludedApplications());
- final AppListDialogFragment fragment = AppListDialogFragment.newInstance(excludedApps, this);
- fragment.show(getParentFragmentManager(), null);
- }
- }
-
- @Override
- public void onSaveInstanceState(final Bundle outState) {
- if (binding != null)
- outState.putParcelable(KEY_LOCAL_CONFIG, binding.getConfig());
- outState.putString(KEY_ORIGINAL_NAME, tunnel == null ? null : tunnel.getName());
- super.onSaveInstanceState(outState);
- }
-
- @Override
- public void onSelectedTunnelChanged(@Nullable final ObservableTunnel oldTunnel,
- @Nullable final ObservableTunnel newTunnel) {
- tunnel = newTunnel;
- if (binding == null)
- return;
- binding.setConfig(new ConfigProxy());
- if (tunnel != null) {
- binding.setName(tunnel.getName());
- tunnel.getConfigAsync().thenAccept(this::onConfigLoaded);
- } else {
- binding.setName("");
- }
- }
-
- private void onTunnelCreated(final ObservableTunnel newTunnel, @Nullable final Throwable throwable) {
- final String message;
- if (throwable == null) {
- tunnel = newTunnel;
- message = getString(R.string.tunnel_create_success, tunnel.getName());
- Log.d(TAG, message);
- Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show();
- onFinished();
- } else {
- final String error = ErrorMessages.get(throwable);
- message = getString(R.string.tunnel_create_error, error);
- Log.e(TAG, message, throwable);
- if (binding != null) {
- Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show();
- }
- }
- }
-
- private void onTunnelRenamed(final ObservableTunnel renamedTunnel, final Config newConfig,
- @Nullable final Throwable throwable) {
- final String message;
- if (throwable == null) {
- message = getString(R.string.tunnel_rename_success, renamedTunnel.getName());
- Log.d(TAG, message);
- // Now save the rest of configuration changes.
- Log.d(TAG, "Attempting to save config of renamed tunnel " + tunnel.getName());
- renamedTunnel.setConfig(newConfig).whenComplete((a, b) -> onConfigSaved(renamedTunnel, b));
- } else {
- final String error = ErrorMessages.get(throwable);
- message = getString(R.string.tunnel_rename_error, error);
- Log.e(TAG, message, throwable);
- if (binding != null) {
- Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show();
- }
- }
- }
-
- @Override
- public void onViewStateRestored(@Nullable final Bundle savedInstanceState) {
- if (binding == null) {
- return;
- }
-
- binding.setFragment(this);
-
- if (savedInstanceState == null) {
- onSelectedTunnelChanged(null, getSelectedTunnel());
- } else {
- tunnel = getSelectedTunnel();
- final ConfigProxy config = savedInstanceState.getParcelable(KEY_LOCAL_CONFIG);
- final String originalName = savedInstanceState.getString(KEY_ORIGINAL_NAME);
- if (tunnel != null && !tunnel.getName().equals(originalName))
- onSelectedTunnelChanged(null, tunnel);
- else
- binding.setConfig(config);
- }
-
- super.onViewStateRestored(savedInstanceState);
- }
-
-}
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 21618e60..00000000
--- a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java
+++ /dev/null
@@ -1,449 +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.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.ObservableTunnel;
-import com.wireguard.android.ui.EdgeToEdge;
-import com.wireguard.android.util.ErrorMessages;
-import com.wireguard.android.widget.MultiselectableRelativeLayout;
-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 {
- public static final int REQUEST_IMPORT = 1;
- private static final int REQUEST_TARGET_FRAGMENT = 2;
- private static final String TAG = "WireGuard/" + TunnelListFragment.class.getSimpleName();
-
- private final ActionModeListener actionModeListener = new ActionModeListener();
- @Nullable private ActionMode actionMode;
- @Nullable private TunnelListFragmentBinding binding;
-
- 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…
- ConfigNamingDialogFragment.newInstance(configText).show(getParentFragmentManager(), 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<ObservableTunnel>> 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<ObservableTunnel> tunnels = new ArrayList<>(futureTunnels.size());
- for (final CompletableFuture<ObservableTunnel> futureTunnel : futureTunnels) {
- ObservableTunnel 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);
- }
- }
-
- @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.createFab.setOnClickListener(v -> {
- final AddTunnelsSheet bottomSheet = new AddTunnelsSheet();
- bottomSheet.setTargetFragment(this, REQUEST_TARGET_FRAGMENT);
- bottomSheet.show(getParentFragmentManager(), "BOTTOM_SHEET");
- });
- binding.executePendingBindings();
- EdgeToEdge.setUpRoot((ViewGroup) binding.getRoot());
- EdgeToEdge.setUpFAB(binding.createFab);
- EdgeToEdge.setUpScrollingContent(binding.tunnelList, binding.createFab);
- return binding.getRoot();
- }
-
- @Override
- public void onDestroyView() {
- binding = null;
- super.onDestroyView();
- }
-
- @Override
- public void onPause() {
- super.onPause();
- }
-
- public void onRequestCreateConfig(@SuppressWarnings("unused") final View view) {
- startActivity(new Intent(getActivity(), TunnelCreatorActivity.class));
- }
-
- @Override
- public void onSaveInstanceState(final Bundle outState) {
- super.onSaveInstanceState(outState);
-
- outState.putIntegerArrayList("CHECKED_ITEMS", actionModeListener.getCheckedItems());
- }
-
- @Override
- public void onSelectedTunnelChanged(@Nullable final ObservableTunnel oldTunnel, @Nullable final ObservableTunnel 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 showSnackbar(final CharSequence message) {
- if (binding != null) {
- final Snackbar snackbar = Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG);
- snackbar.setAnchorView(binding.createFab);
- snackbar.show();
- }
- }
-
- 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);
- }
- showSnackbar(message);
- }
-
- private void onTunnelImportFinished(final List<ObservableTunnel> 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());
-
- showSnackbar(message);
- }
-
- @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, ObservableTunnel>) (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 ObservableTunnel 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<ObservableTunnel> tunnelsToDelete = new ArrayList<>();
- for (final Integer position : copyCheckedItems)
- tunnelsToDelete.add(tunnels.get(position));
-
- final CompletableFuture[] futures = StreamSupport.stream(tunnelsToDelete)
- .map(ObservableTunnel::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));
- }
- }
- }
-
-}