strongswan/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java

349 lines
10 KiB
Java

/*
* Copyright (C) 2012-2019 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
package org.strongswan.android.ui;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.ActionMode;
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.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.Toast;
import org.strongswan.android.R;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.data.VpnProfileDataSource;
import org.strongswan.android.ui.adapter.VpnProfileAdapter;
import org.strongswan.android.utils.Constants;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import androidx.fragment.app.Fragment;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
public class VpnProfileListFragment extends Fragment
{
private static final String SELECTED_KEY = "SELECTED";
private List<VpnProfile> mVpnProfiles;
private VpnProfileDataSource mDataSource;
private VpnProfileAdapter mListAdapter;
private ListView mListView;
private OnVpnProfileSelectedListener mListener;
private HashSet<Integer> mSelected;
private boolean mReadOnly;
private BroadcastReceiver mProfilesChanged = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
long id, ids[];
if ((id = intent.getLongExtra(Constants.VPN_PROFILES_SINGLE, 0)) > 0)
{
VpnProfile profile = mDataSource.getVpnProfile(id);
if (profile != null)
{ /* in case this was an edit, we remove it first */
mVpnProfiles.remove(profile);
mVpnProfiles.add(profile);
mListAdapter.notifyDataSetChanged();
}
}
else if ((ids = intent.getLongArrayExtra(Constants.VPN_PROFILES_MULTIPLE)) != null)
{
for (long i : ids)
{
Iterator<VpnProfile> profiles = mVpnProfiles.iterator();
while (profiles.hasNext())
{
VpnProfile profile = profiles.next();
if (profile.getId() == i)
{
profiles.remove();
break;
}
}
}
mListAdapter.notifyDataSetChanged();
}
}
};
/**
* The activity containing this fragment should implement this interface
*/
public interface OnVpnProfileSelectedListener {
void onVpnProfileSelected(VpnProfile profile);
}
@Override
public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState)
{
super.onInflate(context, attrs, savedInstanceState);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Fragment);
mReadOnly = a.getBoolean(R.styleable.Fragment_read_only, false);
a.recycle();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.profile_list_fragment, null);
mListView = view.findViewById(R.id.profile_list);
mListView.setAdapter(mListAdapter);
mListView.setEmptyView(view.findViewById(R.id.profile_list_empty));
mListView.setOnItemClickListener(mVpnProfileClicked);
if (!mReadOnly)
{
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
mListView.setMultiChoiceModeListener(mVpnProfileSelected);
}
return view;
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (args != null)
{
mReadOnly = args.getBoolean("read_only", mReadOnly);
}
if (!mReadOnly)
{
setHasOptionsMenu(true);
ArrayList<Integer> selected = null;
if (savedInstanceState != null)
{
selected = savedInstanceState.getIntegerArrayList(SELECTED_KEY);
}
mSelected = selected != null ? new HashSet<>(selected) : new HashSet<>();
}
mDataSource = new VpnProfileDataSource(this.getActivity());
mDataSource.open();
/* cached list of profiles used as backend for the ListView */
mVpnProfiles = mDataSource.getAllVpnProfiles();
mListAdapter = new VpnProfileAdapter(getActivity(), R.layout.profile_list_item, mVpnProfiles);
IntentFilter profileChangesFilter = new IntentFilter(Constants.VPN_PROFILES_CHANGED);
LocalBroadcastManager.getInstance(getActivity()).registerReceiver(mProfilesChanged, profileChangesFilter);
}
@Override
public void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
outState.putIntegerArrayList(SELECTED_KEY, new ArrayList<>(mSelected));
}
@Override
public void onDestroy()
{
super.onDestroy();
mDataSource.close();
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(mProfilesChanged);
}
@Override
public void onAttach(Context context)
{
super.onAttach(context);
if (context instanceof OnVpnProfileSelectedListener)
{
mListener = (OnVpnProfileSelectedListener)context;
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
{
inflater.inflate(R.menu.profile_list, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case R.id.add_profile:
Intent connectionIntent = new Intent(getActivity(),
VpnProfileDetailActivity.class);
startActivity(connectionIntent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private final OnItemClickListener mVpnProfileClicked = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> a, View v, int position, long id)
{
if (mListener != null)
{
mListener.onVpnProfileSelected((VpnProfile)a.getItemAtPosition(position));
}
}
};
private final MultiChoiceModeListener mVpnProfileSelected = new MultiChoiceModeListener() {
private MenuItem mEditProfile;
private MenuItem mCopyProfile;
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu)
{
mEditProfile.setEnabled(mSelected.size() == 1);
mCopyProfile.setEnabled(mEditProfile.isEnabled());
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode)
{
mSelected.clear();
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu)
{
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.profile_list_context, menu);
mEditProfile = menu.findItem(R.id.edit_profile);
mCopyProfile = menu.findItem(R.id.copy_profile);
mode.setTitle(R.string.select_profiles);
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item)
{
switch (item.getItemId())
{
case R.id.edit_profile:
{
int position = mSelected.iterator().next();
VpnProfile profile = (VpnProfile)mListView.getItemAtPosition(position);
Intent connectionIntent = new Intent(getActivity(), VpnProfileDetailActivity.class);
connectionIntent.putExtra(VpnProfileDataSource.KEY_ID, profile.getId());
startActivity(connectionIntent);
break;
}
case R.id.copy_profile:
{
int position = mSelected.iterator().next();
VpnProfile profile = (VpnProfile)mListView.getItemAtPosition(position);
profile = profile.clone();
profile.setUUID(UUID.randomUUID());
profile.setName(String.format(getString(R.string.copied_name), profile.getName()));
mDataSource.insertProfile(profile);
Intent intent = new Intent(Constants.VPN_PROFILES_CHANGED);
intent.putExtra(Constants.VPN_PROFILES_SINGLE, profile.getId());
LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent);
Intent connectionIntent = new Intent(getActivity(), VpnProfileDetailActivity.class);
connectionIntent.putExtra(VpnProfileDataSource.KEY_ID, profile.getId());
startActivity(connectionIntent);
break;
}
case R.id.delete_profile:
{
ArrayList<VpnProfile> profiles = new ArrayList<>();
for (int position : mSelected)
{
profiles.add((VpnProfile)mListView.getItemAtPosition(position));
}
long ids[] = new long[profiles.size()];
for (int i = 0; i < profiles.size(); i++)
{
VpnProfile profile = profiles.get(i);
ids[i] = profile.getId();
mDataSource.deleteVpnProfile(profile);
}
Intent intent = new Intent(Constants.VPN_PROFILES_CHANGED);
intent.putExtra(Constants.VPN_PROFILES_MULTIPLE, ids);
LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent);
Toast.makeText(VpnProfileListFragment.this.getActivity(),
R.string.profiles_deleted, Toast.LENGTH_SHORT).show();
break;
}
default:
return false;
}
mode.finish();
return true;
}
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position,
long id, boolean checked)
{
if (checked)
{
mSelected.add(position);
}
else
{
mSelected.remove(position);
}
final int checkedCount = mSelected.size();
switch (checkedCount)
{
case 0:
mode.setSubtitle(R.string.no_profile_selected);
break;
case 1:
mode.setSubtitle(R.string.one_profile_selected);
break;
default:
mode.setSubtitle(String.format(getString(R.string.x_profiles_selected), checkedCount));
break;
}
mode.invalidate();
}
};
}