From 7d48bef70a56d4370856eedab619b1f83ac3d0d0 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Mon, 9 Mar 2020 19:06:11 +0530 Subject: Rename app module to ui Signed-off-by: Harsh Shandilya --- .../wireguard/android/fragment/AddTunnelsSheet.kt | 106 ----- .../android/fragment/AppListDialogFragment.java | 140 ------- .../wireguard/android/fragment/BaseFragment.java | 126 ------ .../fragment/ConfigNamingDialogFragment.java | 118 ------ .../android/fragment/TunnelDetailFragment.java | 162 -------- .../android/fragment/TunnelEditorFragment.java | 264 ------------ .../android/fragment/TunnelListFragment.java | 449 --------------------- 7 files changed, 1365 deletions(-) delete mode 100644 app/src/main/java/com/wireguard/android/fragment/AddTunnelsSheet.kt delete mode 100644 app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java delete mode 100644 app/src/main/java/com/wireguard/android/fragment/BaseFragment.java delete mode 100644 app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java delete mode 100644 app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java delete mode 100644 app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java delete mode 100644 app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java (limited to 'app/src/main/java/com/wireguard/android/fragment') 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 - 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(R.id.create_empty)?.setOnClickListener { - dismiss() - onRequestCreateConfig() - } - dialog.findViewById(R.id.create_from_file)?.setOnClickListener { - dismiss() - onRequestImportConfig() - } - dialog.findViewById(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 appData = new ObservableKeyedArrayList<>(); - private List currentlyExcludedApps = Collections.emptyList(); - - public static - AppListDialogFragment newInstance(final ArrayList 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 resolveInfos = pm.queryIntentActivities(launcherIntent, 0); - - final List 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 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 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 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 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 excludedApps) { - Objects.requireNonNull(binding, "Tried to set excluded apps while no view was loaded"); - final ObservableList 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 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> futureTunnels = new ArrayList<>(); - final List 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 tunnels = new ArrayList<>(futureTunnels.size()); - for (final CompletableFuture 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 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 tunnels, final Collection 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) (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 checkedItems = new HashSet<>(); - - @Nullable private Resources resources; - - public ArrayList 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 copyCheckedItems = new HashSet<>(checkedItems); - Application.getTunnelManager().getTunnels().thenAccept(tunnels -> { - final Collection 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)); - } - } - } - -} -- cgit v1.2.3