Serviceless rewrite, part 1
Signed-off-by: Samuel Holland <samuel@sholland.org>
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
package com.wireguard.android.fragment;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
|
||||
import com.wireguard.android.activity.BaseActivity;
|
||||
import com.wireguard.android.activity.BaseActivity.OnSelectedTunnelChangedListener;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
|
||||
/**
|
||||
* 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 BaseActivity activity;
|
||||
|
||||
protected Tunnel getSelectedTunnel() {
|
||||
return activity != null ? activity.getSelectedTunnel() : 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(final Tunnel tunnel) {
|
||||
if (activity != null)
|
||||
activity.setSelectedTunnel(tunnel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package com.wireguard.android.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.databinding.ObservableField;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
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 com.commonsware.cwac.crossport.design.widget.CoordinatorLayout;
|
||||
import com.commonsware.cwac.crossport.design.widget.Snackbar;
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.databinding.ConfigEditorFragmentBinding;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
import com.wireguard.android.model.TunnelManager;
|
||||
import com.wireguard.android.util.ExceptionLoggers;
|
||||
import com.wireguard.config.Config;
|
||||
|
||||
/**
|
||||
* Fragment for editing a WireGuard configuration.
|
||||
*/
|
||||
|
||||
public class ConfigEditorFragment extends BaseFragment {
|
||||
private static final String KEY_LOCAL_CONFIG = "local_config";
|
||||
private static final String KEY_LOCAL_NAME = "local_name";
|
||||
private static final String TAG = ConfigEditorFragment.class.getSimpleName();
|
||||
|
||||
private final ObservableField<String> localName = new ObservableField<>();
|
||||
private ConfigEditorFragmentBinding binding;
|
||||
private boolean isViewStateRestored;
|
||||
private Config localConfig = new Config();
|
||||
private String originalName;
|
||||
|
||||
private static <T extends Parcelable> T copyParcelable(final T original) {
|
||||
if (original == null)
|
||||
return null;
|
||||
final Parcel parcel = Parcel.obtain();
|
||||
parcel.writeParcelable(original, 0);
|
||||
parcel.setDataPosition(0);
|
||||
final T copy = parcel.readParcelable(original.getClass().getClassLoader());
|
||||
parcel.recycle();
|
||||
return copy;
|
||||
}
|
||||
|
||||
private void onConfigCreated(final Tunnel tunnel, final Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
Log.e(TAG, "Cannot create tunnel", throwable);
|
||||
final String message = "Cannot create tunnel: "
|
||||
+ ExceptionLoggers.unwrap(throwable).getMessage();
|
||||
if (binding != null) {
|
||||
final CoordinatorLayout container = binding.mainContainer;
|
||||
Snackbar.make(container, message, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Successfully created tunnel " + tunnel.getName());
|
||||
onFinished(tunnel);
|
||||
}
|
||||
}
|
||||
|
||||
private void onConfigLoaded(final Config config) {
|
||||
localConfig = copyParcelable(config);
|
||||
if (binding != null && isViewStateRestored)
|
||||
binding.setConfig(localConfig);
|
||||
}
|
||||
|
||||
private void onConfigSaved(final Config config, final Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
Log.e(TAG, "Cannot save configuration", throwable);
|
||||
final String message = "Cannot save configuration: "
|
||||
+ ExceptionLoggers.unwrap(throwable).getMessage();
|
||||
if (binding != null) {
|
||||
final CoordinatorLayout container = binding.mainContainer;
|
||||
Snackbar.make(container, message, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Successfully saved configuration for " + getSelectedTunnel().getName());
|
||||
onFinished(getSelectedTunnel());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (savedInstanceState != null) {
|
||||
localConfig = savedInstanceState.getParcelable(KEY_LOCAL_CONFIG);
|
||||
localName.set(savedInstanceState.getString(KEY_LOCAL_NAME));
|
||||
originalName = savedInstanceState.getString(TunnelManager.KEY_SELECTED_TUNNEL);
|
||||
}
|
||||
// Erase the remains of creating or editing a different tunnel.
|
||||
if (getSelectedTunnel() != null && !getSelectedTunnel().getName().equals(originalName)) {
|
||||
// The config must be loaded asynchronously since it's not an observable property.
|
||||
localConfig = null;
|
||||
getSelectedTunnel().getConfigAsync().thenAccept(this::onConfigLoaded);
|
||||
originalName = getSelectedTunnel().getName();
|
||||
localName.set(originalName);
|
||||
} else if (getSelectedTunnel() == null && originalName != null) {
|
||||
localConfig = new Config();
|
||||
originalName = null;
|
||||
localName.set(null);
|
||||
}
|
||||
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, final ViewGroup container,
|
||||
final Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
binding = ConfigEditorFragmentBinding.inflate(inflater, container, false);
|
||||
binding.executePendingBindings();
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
binding = null;
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
private void onFinished(final Tunnel tunnel) {
|
||||
// Hide the keyboard; it rarely goes away on its own.
|
||||
final Activity activity = getActivity();
|
||||
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(() -> {
|
||||
setSelectedTunnel(null);
|
||||
setSelectedTunnel(tunnel);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_action_save:
|
||||
if (getSelectedTunnel() != null) {
|
||||
Log.d(TAG, "Attempting to save config to " + getSelectedTunnel().getName());
|
||||
getSelectedTunnel().setConfig(localConfig)
|
||||
.whenComplete(this::onConfigSaved);
|
||||
} else {
|
||||
Log.d(TAG, "Attempting to create new tunnel " + localName.get());
|
||||
final TunnelManager manager = Application.getComponent().getTunnelManager();
|
||||
manager.create(localName.get(), localConfig)
|
||||
.whenComplete(this::onConfigCreated);
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(final Bundle outState) {
|
||||
outState.putParcelable(KEY_LOCAL_CONFIG, localConfig);
|
||||
outState.putString(KEY_LOCAL_NAME, localName.get());
|
||||
outState.putString(TunnelManager.KEY_SELECTED_TUNNEL, originalName);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectedTunnelChanged(final Tunnel oldTunnel, final Tunnel newTunnel) {
|
||||
// Erase the remains of creating or editing a different tunnel.
|
||||
if (newTunnel != null) {
|
||||
// The config must be loaded asynchronously since it's not an observable property.
|
||||
localConfig = null;
|
||||
newTunnel.getConfigAsync().thenAccept(this::onConfigLoaded);
|
||||
originalName = newTunnel.getName();
|
||||
} else {
|
||||
localConfig = new Config();
|
||||
if (binding != null && isViewStateRestored)
|
||||
binding.setConfig(localConfig);
|
||||
originalName = null;
|
||||
}
|
||||
localName.set(originalName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewStateRestored(final Bundle savedInstanceState) {
|
||||
super.onViewStateRestored(savedInstanceState);
|
||||
binding.setConfig(localConfig);
|
||||
binding.setName(localName);
|
||||
// FIXME: Remove this when renaming works.
|
||||
binding.interfaceNameText.setEnabled(originalName == null);
|
||||
isViewStateRestored = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.wireguard.android.fragment;
|
||||
|
||||
import android.os.Bundle;
|
||||
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.model.Tunnel;
|
||||
|
||||
/**
|
||||
* Fragment that shows details about a specific tunnel.
|
||||
*/
|
||||
|
||||
public class TunnelDetailFragment extends BaseFragment {
|
||||
private TunnelDetailFragmentBinding binding;
|
||||
private boolean isViewStateRestored;
|
||||
|
||||
@Override
|
||||
public void onCreate(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 View onCreateView(final LayoutInflater inflater, final ViewGroup container,
|
||||
final Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
binding = TunnelDetailFragmentBinding.inflate(inflater, container, false);
|
||||
binding.executePendingBindings();
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
binding = null;
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectedTunnelChanged(final Tunnel oldTunnel, final Tunnel newTunnel) {
|
||||
if (binding != null && isViewStateRestored)
|
||||
binding.setTunnel(newTunnel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewStateRestored(final Bundle savedInstanceState) {
|
||||
super.onViewStateRestored(savedInstanceState);
|
||||
binding.setTunnel(getSelectedTunnel());
|
||||
isViewStateRestored = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
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 android.util.Log;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.ActionMode;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnTouchListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.AbsListView.MultiChoiceModeListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.AdapterView.OnItemLongClickListener;
|
||||
|
||||
import com.commonsware.cwac.crossport.design.widget.CoordinatorLayout;
|
||||
import com.commonsware.cwac.crossport.design.widget.Snackbar;
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.Application.ApplicationComponent;
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.activity.TunnelCreatorActivity;
|
||||
import com.wireguard.android.databinding.TunnelListFragmentBinding;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
import com.wireguard.android.model.TunnelManager;
|
||||
import com.wireguard.android.util.AsyncWorker;
|
||||
import com.wireguard.android.util.ExceptionLoggers;
|
||||
import com.wireguard.config.Config;
|
||||
|
||||
import java9.util.concurrent.CompletableFuture;
|
||||
import java9.util.concurrent.CompletionStage;
|
||||
import java9.util.function.Function;
|
||||
import java9.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* Fragment containing a list of known WireGuard tunnels. It allows creating and deleting tunnels.
|
||||
*/
|
||||
|
||||
public class TunnelListFragment extends BaseFragment {
|
||||
private static final int REQUEST_IMPORT = 1;
|
||||
private static final String TAG = TunnelListFragment.class.getSimpleName();
|
||||
|
||||
private final MultiChoiceModeListener actionModeListener = new ActionModeListener();
|
||||
private final ListViewCallbacks listViewCallbacks = new ListViewCallbacks();
|
||||
private ActionMode actionMode;
|
||||
private AsyncWorker asyncWorker;
|
||||
private TunnelListFragmentBinding binding;
|
||||
private TunnelManager tunnelManager;
|
||||
|
||||
private void importTunnel(final Uri uri) {
|
||||
final Activity activity = getActivity();
|
||||
if (activity == null)
|
||||
return;
|
||||
final ContentResolver contentResolver = activity.getContentResolver();
|
||||
final CompletionStage<String> nameFuture = asyncWorker.supplyAsync(() -> {
|
||||
final String[] columns = {OpenableColumns.DISPLAY_NAME};
|
||||
String name = null;
|
||||
try (final 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());
|
||||
if (name.indexOf('/') >= 0)
|
||||
name = name.substring(name.lastIndexOf('/') + 1);
|
||||
if (name.endsWith(".conf"))
|
||||
name = name.substring(0, name.length() - ".conf".length());
|
||||
Log.d(TAG, "Import mapped URI " + uri + " to tunnel name " + name);
|
||||
return name;
|
||||
});
|
||||
asyncWorker.supplyAsync(() -> Config.from(contentResolver.openInputStream(uri)))
|
||||
.thenCombine(nameFuture, (config, name) -> tunnelManager.create(name, config))
|
||||
.thenCompose(Function.identity())
|
||||
.handle(this::onTunnelImportFinished);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_IMPORT:
|
||||
if (resultCode == Activity.RESULT_OK)
|
||||
importTunnel(data.getData());
|
||||
return;
|
||||
default:
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
final ApplicationComponent applicationComponent = Application.getComponent();
|
||||
asyncWorker = applicationComponent.getAsyncWorker();
|
||||
tunnelManager = applicationComponent.getTunnelManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
|
||||
final Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
binding = TunnelListFragmentBinding.inflate(inflater, container, false);
|
||||
binding.tunnelList.setMultiChoiceModeListener(actionModeListener);
|
||||
binding.tunnelList.setOnItemClickListener(listViewCallbacks);
|
||||
binding.tunnelList.setOnItemLongClickListener(listViewCallbacks);
|
||||
binding.tunnelList.setOnTouchListener(listViewCallbacks);
|
||||
binding.executePendingBindings();
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
binding = null;
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
public void onRequestCreateConfig(@SuppressWarnings("unused") final View view) {
|
||||
startActivity(new Intent(getActivity(), TunnelCreatorActivity.class));
|
||||
binding.createMenu.collapse();
|
||||
}
|
||||
|
||||
public void onRequestImportConfig(@SuppressWarnings("unused") final View view) {
|
||||
final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("*/*");
|
||||
startActivityForResult(intent, REQUEST_IMPORT);
|
||||
binding.createMenu.collapse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectedTunnelChanged(final Tunnel oldTunnel, final Tunnel newTunnel) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
private Void onTunnelDeletionFinished(final Integer count, final Throwable throwable) {
|
||||
final String message;
|
||||
if (throwable == null) {
|
||||
message = "Successfully deleted " + count + " tunnels";
|
||||
} else {
|
||||
message = "Could not delete some tunnels: "
|
||||
+ ExceptionLoggers.unwrap(throwable).getMessage();
|
||||
Log.e(TAG, "Cannot delete tunnel", throwable);
|
||||
}
|
||||
if (binding != null) {
|
||||
final CoordinatorLayout container = binding.mainContainer;
|
||||
Snackbar.make(container, message, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Void onTunnelImportFinished(final Tunnel tunnel, final Throwable throwable) {
|
||||
final String message;
|
||||
if (throwable == null) {
|
||||
message = "Successfully imported tunnel '" + tunnel.getName() + '\'';
|
||||
} else {
|
||||
message = "Cannot import tunnel: "
|
||||
+ ExceptionLoggers.unwrap(throwable).getMessage();
|
||||
Log.e(TAG, "Cannot import tunnel", throwable);
|
||||
}
|
||||
if (binding != null) {
|
||||
final CoordinatorLayout container = binding.mainContainer;
|
||||
Snackbar.make(container, message, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewStateRestored(final Bundle savedInstanceState) {
|
||||
super.onViewStateRestored(savedInstanceState);
|
||||
binding.setFragment(this);
|
||||
binding.setTunnels(tunnelManager.getTunnels());
|
||||
}
|
||||
|
||||
private final class ActionModeListener implements MultiChoiceModeListener {
|
||||
private Resources resources;
|
||||
private AbsListView tunnelList;
|
||||
|
||||
private IntStream getCheckedPositions() {
|
||||
final SparseBooleanArray checkedItemPositions = tunnelList.getCheckedItemPositions();
|
||||
return IntStream.range(0, checkedItemPositions.size())
|
||||
.filter(checkedItemPositions::valueAt)
|
||||
.map(checkedItemPositions::keyAt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_action_delete:
|
||||
final CompletableFuture[] futures = getCheckedPositions()
|
||||
.mapToObj(pos -> (Tunnel) tunnelList.getItemAtPosition(pos))
|
||||
.map(tunnelManager::delete)
|
||||
.toArray(CompletableFuture[]::new);
|
||||
CompletableFuture.allOf(futures)
|
||||
.thenApply(x -> futures.length)
|
||||
.handle(TunnelListFragment.this::onTunnelDeletionFinished);
|
||||
mode.finish();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateActionMode(final ActionMode mode, final Menu menu) {
|
||||
actionMode = mode;
|
||||
resources = getActivity().getResources();
|
||||
tunnelList = binding.tunnelList;
|
||||
mode.getMenuInflater().inflate(R.menu.tunnel_list_action_mode, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(final ActionMode mode) {
|
||||
actionMode = null;
|
||||
resources = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemCheckedStateChanged(final ActionMode mode, final int position,
|
||||
final long id, final boolean checked) {
|
||||
updateTitle(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) {
|
||||
updateTitle(mode);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void updateTitle(final ActionMode mode) {
|
||||
final int count = (int) getCheckedPositions().count();
|
||||
mode.setTitle(resources.getQuantityString(R.plurals.list_delete_title, count, count));
|
||||
}
|
||||
}
|
||||
|
||||
private final class ListViewCallbacks
|
||||
implements OnItemClickListener, OnItemLongClickListener, OnTouchListener {
|
||||
@Override
|
||||
public void onItemClick(final AdapterView<?> parent, final View view,
|
||||
final int position, final long id) {
|
||||
setSelectedTunnel((Tunnel) parent.getItemAtPosition(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onItemLongClick(final AdapterView<?> parent, final View view,
|
||||
final int position, final long id) {
|
||||
if (actionMode != null)
|
||||
return false;
|
||||
binding.tunnelList.setItemChecked(position, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public boolean onTouch(final View view, final MotionEvent motionEvent) {
|
||||
binding.createMenu.collapse();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user