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.core.view.MenuProvider;
48 import androidx.fragment.app.ListFragment;
49 import androidx.loader.app.LoaderManager;
50 import androidx.loader.content.AsyncTaskLoader;
51 import androidx.loader.content.Loader;
53 public class SelectedApplicationsListFragment extends ListFragment implements MenuProvider, LoaderManager.LoaderCallbacks<Pair<List<SelectedApplicationEntry>, List<String>>>, SearchView.OnQueryTextListener
55 private SelectedApplicationsAdapter mAdapter;
56 private SortedSet<String> mSelection;
59 public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
61 super.onViewCreated(view, savedInstanceState);
62 requireActivity().addMenuProvider(this, getViewLifecycleOwner());
64 final boolean readOnly = getActivity().getIntent().getBooleanExtra(VpnProfileDataSource.KEY_READ_ONLY, false);
65 getListView().setChoiceMode(readOnly ? ListView.CHOICE_MODE_NONE : ListView.CHOICE_MODE_MULTIPLE);
67 mAdapter = new SelectedApplicationsAdapter(getActivity());
68 mAdapter.setReadOnly(readOnly);
69 setListAdapter(mAdapter);
72 ArrayList<String> selection;
73 if (savedInstanceState == null)
75 selection = getActivity().getIntent().getStringArrayListExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
79 selection = savedInstanceState.getStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
81 mSelection = new TreeSet<>(selection);
83 LoaderManager.getInstance(this).initLoader(0, null, this);
87 public void onSaveInstanceState(Bundle outState)
89 super.onSaveInstanceState(outState);
90 outState.putStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelection));
94 * Returns the package names of all selected apps
96 public ArrayList<String> getSelectedApplications()
98 return new ArrayList<>(mSelection);
102 * Track check state as ListView is unable to do that when using filters
105 public void onListItemClick(ListView l, View v, int position, long id)
107 if (mAdapter.isReadOnly())
112 super.onListItemClick(l, v, position, id);
113 SelectedApplicationEntry item = (SelectedApplicationEntry)getListView().getItemAtPosition(position);
114 item.setSelected(!item.isSelected());
115 if (item.isSelected())
117 mSelection.add(item.getInfo().packageName);
121 mSelection.remove(item.getInfo().packageName);
126 * Manually set the check state as ListView is unable to track that when using filters
128 private void setCheckState()
130 for (int i = 0; i < getListView().getCount(); i++)
132 SelectedApplicationEntry item = mAdapter.getItem(i);
133 getListView().setItemChecked(i, item.isSelected());
138 public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater)
140 MenuItem item = menu.add(R.string.search);
141 item.setIcon(android.R.drawable.ic_menu_search);
142 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
144 SearchView sv = new SearchView(getActivity());
145 sv.setOnQueryTextListener(this);
146 item.setActionView(sv);
150 public boolean onMenuItemSelected(@NonNull MenuItem menuItem)
156 public Loader<Pair<List<SelectedApplicationEntry>, List<String>>> onCreateLoader(int id, Bundle args)
158 return new InstalledPackagesLoader(getActivity(), mSelection);
162 public void onLoadFinished(Loader<Pair<List<SelectedApplicationEntry>, List<String>>> loader, Pair<List<SelectedApplicationEntry>, List<String>> data)
164 mAdapter.setData(data.first);
165 mSelection.removeAll(data.second);
174 setListShownNoAnimation(true);
179 public void onLoaderReset(Loader<Pair<List<SelectedApplicationEntry>, List<String>>> loader)
181 mAdapter.setData(null);
185 public boolean onQueryTextSubmit(String query)
191 public boolean onQueryTextChange(String newText)
193 String search = TextUtils.isEmpty(newText) ? null : newText;
194 mAdapter.getFilter().filter(search, new Filter.FilterListener()
197 public void onFilterComplete(int count)
205 public static class InstalledPackagesLoader extends AsyncTaskLoader<Pair<List<SelectedApplicationEntry>, List<String>>>
207 private final PackageManager mPackageManager;
208 private final SortedSet<String> mSelection;
209 private Pair<List<SelectedApplicationEntry>, List<String>> mData;
211 public InstalledPackagesLoader(Context context, SortedSet<String> selection)
214 mPackageManager = context.getPackageManager();
215 mSelection = selection;
219 public Pair<List<SelectedApplicationEntry>, List<String>> loadInBackground()
221 List<SelectedApplicationEntry> apps = new ArrayList<>();
222 SortedSet<String> seen = new TreeSet<>();
223 for (ApplicationInfo info : mPackageManager.getInstalledApplications(PackageManager.GET_META_DATA))
225 /* skip apps that can't access the network anyway */
226 if (mPackageManager.checkPermission(Manifest.permission.INTERNET, info.packageName) == PackageManager.PERMISSION_GRANTED)
228 SelectedApplicationEntry entry = new SelectedApplicationEntry(mPackageManager, info);
229 entry.setSelected(mSelection.contains(info.packageName));
231 seen.add(info.packageName);
234 Collections.sort(apps);
235 /* check for selected packages that don't exist anymore */
236 List<String> missing = new ArrayList<>();
237 for (String pkg : mSelection)
239 if (!seen.contains(pkg))
244 return new Pair<>(apps, missing);
248 protected void onStartLoading()
251 { /* if we have data ready, deliver it directly */
252 deliverResult(mData);
254 if (takeContentChanged() || mData == null)
261 public void deliverResult(Pair<List<SelectedApplicationEntry>, List<String>> data)
269 { /* if it is started we deliver the data directly,
270 * otherwise this is handled in onStartLoading */
271 super.deliverResult(data);
276 protected void onReset()