2 * Copyright (C) 2017 Tobias Brunner
3 * HSR Hochschule fuer Technik Rapperswil
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 package org.strongswan.android.ui;
18 import android.Manifest;
19 import android.content.Context;
20 import android.content.pm.ApplicationInfo;
21 import android.content.pm.PackageManager;
22 import android.os.Bundle;
23 import android.text.TextUtils;
24 import android.util.Pair;
25 import android.view.Menu;
26 import android.view.MenuInflater;
27 import android.view.MenuItem;
28 import android.view.View;
29 import android.widget.Filter;
30 import android.widget.ListView;
32 import org.strongswan.android.R;
33 import org.strongswan.android.data.VpnProfileDataSource;
34 import org.strongswan.android.ui.adapter.SelectedApplicationEntry;
35 import org.strongswan.android.ui.adapter.SelectedApplicationsAdapter;
37 import java.util.ArrayList;
38 import java.util.Collections;
39 import java.util.List;
40 import java.util.SortedSet;
41 import java.util.TreeSet;
43 import androidx.annotation.Nullable;
44 import androidx.appcompat.widget.SearchView;
45 import androidx.fragment.app.ListFragment;
46 import androidx.loader.app.LoaderManager;
47 import androidx.loader.content.AsyncTaskLoader;
48 import androidx.loader.content.Loader;
50 public class SelectedApplicationsListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Pair<List<SelectedApplicationEntry>, List<String>>>, SearchView.OnQueryTextListener
52 private SelectedApplicationsAdapter mAdapter;
53 private SortedSet<String> mSelection;
56 public void onActivityCreated(@Nullable Bundle savedInstanceState)
58 super.onActivityCreated(savedInstanceState);
59 setHasOptionsMenu(true);
61 getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
63 mAdapter = new SelectedApplicationsAdapter(getActivity());
64 setListAdapter(mAdapter);
67 ArrayList<String> selection;
68 if (savedInstanceState == null)
70 selection = getActivity().getIntent().getStringArrayListExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
74 selection = savedInstanceState.getStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
76 mSelection = new TreeSet<>(selection);
78 LoaderManager.getInstance(this).initLoader(0, null, this);
82 public void onSaveInstanceState(Bundle outState)
84 super.onSaveInstanceState(outState);
85 outState.putStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelection));
89 * Returns the package names of all selected apps
91 public ArrayList<String> getSelectedApplications()
93 return new ArrayList<>(mSelection);
97 * Track check state as ListView is unable to do that when using filters
100 public void onListItemClick(ListView l, View v, int position, long id)
102 super.onListItemClick(l, v, position, id);
103 SelectedApplicationEntry item = (SelectedApplicationEntry)getListView().getItemAtPosition(position);
104 item.setSelected(!item.isSelected());
105 if (item.isSelected())
107 mSelection.add(item.getInfo().packageName);
111 mSelection.remove(item.getInfo().packageName);
116 * Manually set the check state as ListView is unable to track that when using filters
118 private void setCheckState()
120 for (int i = 0; i < getListView().getCount(); i++)
122 SelectedApplicationEntry item = mAdapter.getItem(i);
123 getListView().setItemChecked(i, item.isSelected());
128 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
130 MenuItem item = menu.add(R.string.search);
131 item.setIcon(android.R.drawable.ic_menu_search);
132 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
134 SearchView sv = new SearchView(getActivity());
135 sv.setOnQueryTextListener(this);
136 item.setActionView(sv);
138 super.onCreateOptionsMenu(menu, inflater);
142 public Loader<Pair<List<SelectedApplicationEntry>, List<String>>> onCreateLoader(int id, Bundle args)
144 return new InstalledPackagesLoader(getActivity(), mSelection);
148 public void onLoadFinished(Loader<Pair<List<SelectedApplicationEntry>, List<String>>> loader, Pair<List<SelectedApplicationEntry>, List<String>> data)
150 mAdapter.setData(data.first);
151 mSelection.removeAll(data.second);
160 setListShownNoAnimation(true);
165 public void onLoaderReset(Loader<Pair<List<SelectedApplicationEntry>, List<String>>> loader)
167 mAdapter.setData(null);
171 public boolean onQueryTextSubmit(String query)
177 public boolean onQueryTextChange(String newText)
179 String search = TextUtils.isEmpty(newText) ? null : newText;
180 mAdapter.getFilter().filter(search, new Filter.FilterListener()
183 public void onFilterComplete(int count)
191 public static class InstalledPackagesLoader extends AsyncTaskLoader<Pair<List<SelectedApplicationEntry>, List<String>>>
193 private final PackageManager mPackageManager;
194 private final SortedSet<String> mSelection;
195 private Pair<List<SelectedApplicationEntry>, List<String>> mData;
197 public InstalledPackagesLoader(Context context, SortedSet<String> selection)
200 mPackageManager = context.getPackageManager();
201 mSelection = selection;
205 public Pair<List<SelectedApplicationEntry>, List<String>> loadInBackground()
207 List<SelectedApplicationEntry> apps = new ArrayList<>();
208 SortedSet<String> seen = new TreeSet<>();
209 for (ApplicationInfo info : mPackageManager.getInstalledApplications(PackageManager.GET_META_DATA))
211 /* skip apps that can't access the network anyway */
212 if (mPackageManager.checkPermission(Manifest.permission.INTERNET, info.packageName) == PackageManager.PERMISSION_GRANTED)
214 SelectedApplicationEntry entry = new SelectedApplicationEntry(mPackageManager, info);
215 entry.setSelected(mSelection.contains(info.packageName));
217 seen.add(info.packageName);
220 Collections.sort(apps);
221 /* check for selected packages that don't exist anymore */
222 List<String> missing = new ArrayList<>();
223 for (String pkg : mSelection)
225 if (!seen.contains(pkg))
230 return new Pair<>(apps, missing);
234 protected void onStartLoading()
237 { /* if we have data ready, deliver it directly */
238 deliverResult(mData);
240 if (takeContentChanged() || mData == null)
247 public void deliverResult(Pair<List<SelectedApplicationEntry>, List<String>> data)
255 { /* if it is started we deliver the data directly,
256 * otherwise this is handled in onStartLoading */
257 super.deliverResult(data);
262 protected void onReset()