2 * Copyright (C) 2017 Tobias Brunner
4 * Copyright (C) secunet Security Networks AG
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2 of the License, or (at your
9 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 package org.strongswan.android.ui;
19 import android.Manifest;
20 import android.content.Context;
21 import android.content.pm.ApplicationInfo;
22 import android.content.pm.PackageManager;
23 import android.os.Bundle;
24 import android.text.TextUtils;
25 import android.util.Pair;
26 import android.view.Menu;
27 import android.view.MenuInflater;
28 import android.view.MenuItem;
29 import android.view.View;
30 import android.widget.Filter;
31 import android.widget.ListView;
33 import org.strongswan.android.R;
34 import org.strongswan.android.data.VpnProfileDataSource;
35 import org.strongswan.android.ui.adapter.SelectedApplicationEntry;
36 import org.strongswan.android.ui.adapter.SelectedApplicationsAdapter;
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.List;
41 import java.util.SortedSet;
42 import java.util.TreeSet;
44 import androidx.annotation.NonNull;
45 import androidx.annotation.Nullable;
46 import androidx.appcompat.widget.SearchView;
47 import androidx.fragment.app.ListFragment;
48 import androidx.loader.app.LoaderManager;
49 import androidx.loader.content.AsyncTaskLoader;
50 import androidx.loader.content.Loader;
52 public class SelectedApplicationsListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Pair<List<SelectedApplicationEntry>, List<String>>>, SearchView.OnQueryTextListener
54 private SelectedApplicationsAdapter mAdapter;
55 private SortedSet<String> mSelection;
58 public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
60 super.onViewCreated(view, savedInstanceState);
61 setHasOptionsMenu(true);
63 getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
65 mAdapter = new SelectedApplicationsAdapter(getActivity());
66 setListAdapter(mAdapter);
69 ArrayList<String> selection;
70 if (savedInstanceState == null)
72 selection = getActivity().getIntent().getStringArrayListExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
76 selection = savedInstanceState.getStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
78 mSelection = new TreeSet<>(selection);
80 LoaderManager.getInstance(this).initLoader(0, null, this);
84 public void onSaveInstanceState(Bundle outState)
86 super.onSaveInstanceState(outState);
87 outState.putStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelection));
91 * Returns the package names of all selected apps
93 public ArrayList<String> getSelectedApplications()
95 return new ArrayList<>(mSelection);
99 * Track check state as ListView is unable to do that when using filters
102 public void onListItemClick(ListView l, View v, int position, long id)
104 super.onListItemClick(l, v, position, id);
105 SelectedApplicationEntry item = (SelectedApplicationEntry)getListView().getItemAtPosition(position);
106 item.setSelected(!item.isSelected());
107 if (item.isSelected())
109 mSelection.add(item.getInfo().packageName);
113 mSelection.remove(item.getInfo().packageName);
118 * Manually set the check state as ListView is unable to track that when using filters
120 private void setCheckState()
122 for (int i = 0; i < getListView().getCount(); i++)
124 SelectedApplicationEntry item = mAdapter.getItem(i);
125 getListView().setItemChecked(i, item.isSelected());
130 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
132 MenuItem item = menu.add(R.string.search);
133 item.setIcon(android.R.drawable.ic_menu_search);
134 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
136 SearchView sv = new SearchView(getActivity());
137 sv.setOnQueryTextListener(this);
138 item.setActionView(sv);
140 super.onCreateOptionsMenu(menu, inflater);
144 public Loader<Pair<List<SelectedApplicationEntry>, List<String>>> onCreateLoader(int id, Bundle args)
146 return new InstalledPackagesLoader(getActivity(), mSelection);
150 public void onLoadFinished(Loader<Pair<List<SelectedApplicationEntry>, List<String>>> loader, Pair<List<SelectedApplicationEntry>, List<String>> data)
152 mAdapter.setData(data.first);
153 mSelection.removeAll(data.second);
162 setListShownNoAnimation(true);
167 public void onLoaderReset(Loader<Pair<List<SelectedApplicationEntry>, List<String>>> loader)
169 mAdapter.setData(null);
173 public boolean onQueryTextSubmit(String query)
179 public boolean onQueryTextChange(String newText)
181 String search = TextUtils.isEmpty(newText) ? null : newText;
182 mAdapter.getFilter().filter(search, new Filter.FilterListener()
185 public void onFilterComplete(int count)
193 public static class InstalledPackagesLoader extends AsyncTaskLoader<Pair<List<SelectedApplicationEntry>, List<String>>>
195 private final PackageManager mPackageManager;
196 private final SortedSet<String> mSelection;
197 private Pair<List<SelectedApplicationEntry>, List<String>> mData;
199 public InstalledPackagesLoader(Context context, SortedSet<String> selection)
202 mPackageManager = context.getPackageManager();
203 mSelection = selection;
207 public Pair<List<SelectedApplicationEntry>, List<String>> loadInBackground()
209 List<SelectedApplicationEntry> apps = new ArrayList<>();
210 SortedSet<String> seen = new TreeSet<>();
211 for (ApplicationInfo info : mPackageManager.getInstalledApplications(PackageManager.GET_META_DATA))
213 /* skip apps that can't access the network anyway */
214 if (mPackageManager.checkPermission(Manifest.permission.INTERNET, info.packageName) == PackageManager.PERMISSION_GRANTED)
216 SelectedApplicationEntry entry = new SelectedApplicationEntry(mPackageManager, info);
217 entry.setSelected(mSelection.contains(info.packageName));
219 seen.add(info.packageName);
222 Collections.sort(apps);
223 /* check for selected packages that don't exist anymore */
224 List<String> missing = new ArrayList<>();
225 for (String pkg : mSelection)
227 if (!seen.contains(pkg))
232 return new Pair<>(apps, missing);
236 protected void onStartLoading()
239 { /* if we have data ready, deliver it directly */
240 deliverResult(mData);
242 if (takeContentChanged() || mData == null)
249 public void deliverResult(Pair<List<SelectedApplicationEntry>, List<String>> data)
257 { /* if it is started we deliver the data directly,
258 * otherwise this is handled in onStartLoading */
259 super.deliverResult(data);
264 protected void onReset()