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

270 lines
7.4 KiB
Java

/*
* Copyright (C) 2017 Tobias Brunner
* 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.Manifest;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Pair;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Filter;
import android.widget.ListView;
import org.strongswan.android.R;
import org.strongswan.android.data.VpnProfileDataSource;
import org.strongswan.android.ui.adapter.SelectedApplicationEntry;
import org.strongswan.android.ui.adapter.SelectedApplicationsAdapter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.SearchView;
import androidx.fragment.app.ListFragment;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.AsyncTaskLoader;
import androidx.loader.content.Loader;
public class SelectedApplicationsListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Pair<List<SelectedApplicationEntry>, List<String>>>, SearchView.OnQueryTextListener
{
private SelectedApplicationsAdapter mAdapter;
private SortedSet<String> mSelection;
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
setHasOptionsMenu(true);
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
mAdapter = new SelectedApplicationsAdapter(getActivity());
setListAdapter(mAdapter);
setListShown(false);
ArrayList<String> selection;
if (savedInstanceState == null)
{
selection = getActivity().getIntent().getStringArrayListExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
}
else
{
selection = savedInstanceState.getStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
}
mSelection = new TreeSet<>(selection);
LoaderManager.getInstance(this).initLoader(0, null, this);
}
@Override
public void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
outState.putStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelection));
}
/**
* Returns the package names of all selected apps
*/
public ArrayList<String> getSelectedApplications()
{
return new ArrayList<>(mSelection);
}
/**
* Track check state as ListView is unable to do that when using filters
*/
@Override
public void onListItemClick(ListView l, View v, int position, long id)
{
super.onListItemClick(l, v, position, id);
SelectedApplicationEntry item = (SelectedApplicationEntry)getListView().getItemAtPosition(position);
item.setSelected(!item.isSelected());
if (item.isSelected())
{
mSelection.add(item.getInfo().packageName);
}
else
{
mSelection.remove(item.getInfo().packageName);
}
}
/**
* Manually set the check state as ListView is unable to track that when using filters
*/
private void setCheckState()
{
for (int i = 0; i < getListView().getCount(); i++)
{
SelectedApplicationEntry item = mAdapter.getItem(i);
getListView().setItemChecked(i, item.isSelected());
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
{
MenuItem item = menu.add(R.string.search);
item.setIcon(android.R.drawable.ic_menu_search);
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
SearchView sv = new SearchView(getActivity());
sv.setOnQueryTextListener(this);
item.setActionView(sv);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public Loader<Pair<List<SelectedApplicationEntry>, List<String>>> onCreateLoader(int id, Bundle args)
{
return new InstalledPackagesLoader(getActivity(), mSelection);
}
@Override
public void onLoadFinished(Loader<Pair<List<SelectedApplicationEntry>, List<String>>> loader, Pair<List<SelectedApplicationEntry>, List<String>> data)
{
mAdapter.setData(data.first);
mSelection.removeAll(data.second);
setCheckState();
if (isResumed())
{
setListShown(true);
}
else
{
setListShownNoAnimation(true);
}
}
@Override
public void onLoaderReset(Loader<Pair<List<SelectedApplicationEntry>, List<String>>> loader)
{
mAdapter.setData(null);
}
@Override
public boolean onQueryTextSubmit(String query)
{
return true;
}
@Override
public boolean onQueryTextChange(String newText)
{
String search = TextUtils.isEmpty(newText) ? null : newText;
mAdapter.getFilter().filter(search, new Filter.FilterListener()
{
@Override
public void onFilterComplete(int count)
{
setCheckState();
}
});
return true;
}
public static class InstalledPackagesLoader extends AsyncTaskLoader<Pair<List<SelectedApplicationEntry>, List<String>>>
{
private final PackageManager mPackageManager;
private final SortedSet<String> mSelection;
private Pair<List<SelectedApplicationEntry>, List<String>> mData;
public InstalledPackagesLoader(Context context, SortedSet<String> selection)
{
super(context);
mPackageManager = context.getPackageManager();
mSelection = selection;
}
@Override
public Pair<List<SelectedApplicationEntry>, List<String>> loadInBackground()
{
List<SelectedApplicationEntry> apps = new ArrayList<>();
SortedSet<String> seen = new TreeSet<>();
for (ApplicationInfo info : mPackageManager.getInstalledApplications(PackageManager.GET_META_DATA))
{
/* skip apps that can't access the network anyway */
if (mPackageManager.checkPermission(Manifest.permission.INTERNET, info.packageName) == PackageManager.PERMISSION_GRANTED)
{
SelectedApplicationEntry entry = new SelectedApplicationEntry(mPackageManager, info);
entry.setSelected(mSelection.contains(info.packageName));
apps.add(entry);
seen.add(info.packageName);
}
}
Collections.sort(apps);
/* check for selected packages that don't exist anymore */
List<String> missing = new ArrayList<>();
for (String pkg : mSelection)
{
if (!seen.contains(pkg))
{
missing.add(pkg);
}
}
return new Pair<>(apps, missing);
}
@Override
protected void onStartLoading()
{
if (mData != null)
{ /* if we have data ready, deliver it directly */
deliverResult(mData);
}
if (takeContentChanged() || mData == null)
{
forceLoad();
}
}
@Override
public void deliverResult(Pair<List<SelectedApplicationEntry>, List<String>> data)
{
if (isReset())
{
return;
}
mData = data;
if (isStarted())
{ /* if it is started we deliver the data directly,
* otherwise this is handled in onStartLoading */
super.deliverResult(data);
}
}
@Override
protected void onReset()
{
mData = null;
super.onReset();
}
}
}