diff options
author | Tobias Brunner <tobias@strongswan.org> | 2017-06-27 15:10:41 +0200 |
---|---|---|
committer | Tobias Brunner <tobias@strongswan.org> | 2017-07-03 10:27:54 +0200 |
commit | eb59c6a38a7b0038ea3abc9df39f5a5ed6971206 (patch) | |
tree | e37da6ff57bc0fffc21ce77560a904b5759904d5 /src/frontends/android/app | |
parent | 208e15c0bab77b609c844c68408550eace537e2d (diff) | |
download | strongswan-eb59c6a38a7b0038ea3abc9df39f5a5ed6971206.tar.bz2 strongswan-eb59c6a38a7b0038ea3abc9df39f5a5ed6971206.tar.xz |
android: Add list fragment for the selection of apps
Diffstat (limited to 'src/frontends/android/app')
-rw-r--r-- | src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java new file mode 100644 index 000000000..38c7494d5 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java @@ -0,0 +1,267 @@ +/* + * 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.support.annotation.Nullable; +import android.support.v4.app.ListFragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.AsyncTaskLoader; +import android.support.v4.content.Loader; +import android.support.v7.widget.SearchView; +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; + +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 onActivityCreated(@Nullable Bundle savedInstanceState) + { + super.onActivityCreated(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); + + getLoaderManager().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(); + } + } +} |