]> git.ipfire.org Git - thirdparty/strongswan.git/blob
89b5815301e8453b4c411d8f0e5e16a1dd4f3f2f
[thirdparty/strongswan.git] /
1 /*
2 * Copyright (C) 2017 Tobias Brunner
3 *
4 * Copyright (C) secunet Security Networks AG
5 *
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>.
10 *
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
14 * for more details.
15 */
16
17 package org.strongswan.android.ui;
18
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;
32
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;
37
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;
43
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;
52
53 public class SelectedApplicationsListFragment extends ListFragment implements MenuProvider, LoaderManager.LoaderCallbacks<Pair<List<SelectedApplicationEntry>, List<String>>>, SearchView.OnQueryTextListener
54 {
55 private SelectedApplicationsAdapter mAdapter;
56 private SortedSet<String> mSelection;
57
58 @Override
59 public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
60 {
61 super.onViewCreated(view, savedInstanceState);
62 requireActivity().addMenuProvider(this, getViewLifecycleOwner());
63
64 final boolean readOnly = getActivity().getIntent().getBooleanExtra(VpnProfileDataSource.KEY_READ_ONLY, false);
65 getListView().setChoiceMode(readOnly ? ListView.CHOICE_MODE_NONE : ListView.CHOICE_MODE_MULTIPLE);
66
67 mAdapter = new SelectedApplicationsAdapter(getActivity());
68 mAdapter.setReadOnly(readOnly);
69 setListAdapter(mAdapter);
70 setListShown(false);
71
72 ArrayList<String> selection;
73 if (savedInstanceState == null)
74 {
75 selection = getActivity().getIntent().getStringArrayListExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
76 }
77 else
78 {
79 selection = savedInstanceState.getStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
80 }
81 mSelection = new TreeSet<>(selection);
82
83 LoaderManager.getInstance(this).initLoader(0, null, this);
84 }
85
86 @Override
87 public void onSaveInstanceState(Bundle outState)
88 {
89 super.onSaveInstanceState(outState);
90 outState.putStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelection));
91 }
92
93 /**
94 * Returns the package names of all selected apps
95 */
96 public ArrayList<String> getSelectedApplications()
97 {
98 return new ArrayList<>(mSelection);
99 }
100
101 /**
102 * Track check state as ListView is unable to do that when using filters
103 */
104 @Override
105 public void onListItemClick(ListView l, View v, int position, long id)
106 {
107 if (mAdapter.isReadOnly())
108 {
109 return;
110 }
111
112 super.onListItemClick(l, v, position, id);
113 SelectedApplicationEntry item = (SelectedApplicationEntry)getListView().getItemAtPosition(position);
114 item.setSelected(!item.isSelected());
115 if (item.isSelected())
116 {
117 mSelection.add(item.getInfo().packageName);
118 }
119 else
120 {
121 mSelection.remove(item.getInfo().packageName);
122 }
123 }
124
125 /**
126 * Manually set the check state as ListView is unable to track that when using filters
127 */
128 private void setCheckState()
129 {
130 for (int i = 0; i < getListView().getCount(); i++)
131 {
132 SelectedApplicationEntry item = mAdapter.getItem(i);
133 getListView().setItemChecked(i, item.isSelected());
134 }
135 }
136
137 @Override
138 public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater)
139 {
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);
143
144 SearchView sv = new SearchView(getActivity());
145 sv.setOnQueryTextListener(this);
146 item.setActionView(sv);
147 }
148
149 @Override
150 public boolean onMenuItemSelected(@NonNull MenuItem menuItem)
151 {
152 return false;
153 }
154
155 @Override
156 public Loader<Pair<List<SelectedApplicationEntry>, List<String>>> onCreateLoader(int id, Bundle args)
157 {
158 return new InstalledPackagesLoader(getActivity(), mSelection);
159 }
160
161 @Override
162 public void onLoadFinished(Loader<Pair<List<SelectedApplicationEntry>, List<String>>> loader, Pair<List<SelectedApplicationEntry>, List<String>> data)
163 {
164 mAdapter.setData(data.first);
165 mSelection.removeAll(data.second);
166 setCheckState();
167
168 if (isResumed())
169 {
170 setListShown(true);
171 }
172 else
173 {
174 setListShownNoAnimation(true);
175 }
176 }
177
178 @Override
179 public void onLoaderReset(Loader<Pair<List<SelectedApplicationEntry>, List<String>>> loader)
180 {
181 mAdapter.setData(null);
182 }
183
184 @Override
185 public boolean onQueryTextSubmit(String query)
186 {
187 return true;
188 }
189
190 @Override
191 public boolean onQueryTextChange(String newText)
192 {
193 String search = TextUtils.isEmpty(newText) ? null : newText;
194 mAdapter.getFilter().filter(search, new Filter.FilterListener()
195 {
196 @Override
197 public void onFilterComplete(int count)
198 {
199 setCheckState();
200 }
201 });
202 return true;
203 }
204
205 public static class InstalledPackagesLoader extends AsyncTaskLoader<Pair<List<SelectedApplicationEntry>, List<String>>>
206 {
207 private final PackageManager mPackageManager;
208 private final SortedSet<String> mSelection;
209 private Pair<List<SelectedApplicationEntry>, List<String>> mData;
210
211 public InstalledPackagesLoader(Context context, SortedSet<String> selection)
212 {
213 super(context);
214 mPackageManager = context.getPackageManager();
215 mSelection = selection;
216 }
217
218 @Override
219 public Pair<List<SelectedApplicationEntry>, List<String>> loadInBackground()
220 {
221 List<SelectedApplicationEntry> apps = new ArrayList<>();
222 SortedSet<String> seen = new TreeSet<>();
223 for (ApplicationInfo info : mPackageManager.getInstalledApplications(PackageManager.GET_META_DATA))
224 {
225 /* skip apps that can't access the network anyway */
226 if (mPackageManager.checkPermission(Manifest.permission.INTERNET, info.packageName) == PackageManager.PERMISSION_GRANTED)
227 {
228 SelectedApplicationEntry entry = new SelectedApplicationEntry(mPackageManager, info);
229 entry.setSelected(mSelection.contains(info.packageName));
230 apps.add(entry);
231 seen.add(info.packageName);
232 }
233 }
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)
238 {
239 if (!seen.contains(pkg))
240 {
241 missing.add(pkg);
242 }
243 }
244 return new Pair<>(apps, missing);
245 }
246
247 @Override
248 protected void onStartLoading()
249 {
250 if (mData != null)
251 { /* if we have data ready, deliver it directly */
252 deliverResult(mData);
253 }
254 if (takeContentChanged() || mData == null)
255 {
256 forceLoad();
257 }
258 }
259
260 @Override
261 public void deliverResult(Pair<List<SelectedApplicationEntry>, List<String>> data)
262 {
263 if (isReset())
264 {
265 return;
266 }
267 mData = data;
268 if (isStarted())
269 { /* if it is started we deliver the data directly,
270 * otherwise this is handled in onStartLoading */
271 super.deliverResult(data);
272 }
273 }
274
275 @Override
276 protected void onReset()
277 {
278 mData = null;
279 super.onReset();
280 }
281 }
282 }