From eb59c6a38a7b0038ea3abc9df39f5a5ed6971206 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 27 Jun 2017 15:10:41 +0200 Subject: android: Add list fragment for the selection of apps --- .../ui/SelectedApplicationsListFragment.java | 267 +++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java (limited to 'src') 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 . + * + * 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, List>>, SearchView.OnQueryTextListener +{ + private SelectedApplicationsAdapter mAdapter; + private SortedSet 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 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 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, List>> onCreateLoader(int id, Bundle args) + { + return new InstalledPackagesLoader(getActivity(), mSelection); + } + + @Override + public void onLoadFinished(Loader, List>> loader, Pair, List> data) + { + mAdapter.setData(data.first); + mSelection.removeAll(data.second); + setCheckState(); + + if (isResumed()) + { + setListShown(true); + } + else + { + setListShownNoAnimation(true); + } + } + + @Override + public void onLoaderReset(Loader, List>> 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, List>> + { + private final PackageManager mPackageManager; + private final SortedSet mSelection; + private Pair, List> mData; + + public InstalledPackagesLoader(Context context, SortedSet selection) + { + super(context); + mPackageManager = context.getPackageManager(); + mSelection = selection; + } + + @Override + public Pair, List> loadInBackground() + { + List apps = new ArrayList<>(); + SortedSet 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 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> 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(); + } + } +} -- cgit v1.2.3