android: Add menu option to copy a profile
[strongswan.git] / src / frontends / android / app / src / main / java / org / strongswan / android / ui / VpnProfileListFragment.java
1 /*
2 * Copyright (C) 2012-2019 Tobias Brunner
3 * Copyright (C) 2012 Giuliano Grassi
4 * Copyright (C) 2012 Ralf Sager
5 * HSR Hochschule fuer Technik Rapperswil
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation; either version 2 of the License, or (at your
10 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 * for more details.
16 */
17
18 package org.strongswan.android.ui;
19
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.content.res.TypedArray;
25 import android.os.Bundle;
26 import android.support.v4.app.Fragment;
27 import android.support.v4.content.LocalBroadcastManager;
28 import android.util.AttributeSet;
29 import android.view.ActionMode;
30 import android.view.LayoutInflater;
31 import android.view.Menu;
32 import android.view.MenuInflater;
33 import android.view.MenuItem;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.widget.AbsListView.MultiChoiceModeListener;
37 import android.widget.AdapterView;
38 import android.widget.AdapterView.OnItemClickListener;
39 import android.widget.ListView;
40 import android.widget.Toast;
41
42 import org.strongswan.android.R;
43 import org.strongswan.android.data.VpnProfile;
44 import org.strongswan.android.data.VpnProfileDataSource;
45 import org.strongswan.android.ui.adapter.VpnProfileAdapter;
46 import org.strongswan.android.utils.Constants;
47
48 import java.util.ArrayList;
49 import java.util.HashSet;
50 import java.util.Iterator;
51 import java.util.List;
52 import java.util.UUID;
53
54 public class VpnProfileListFragment extends Fragment
55 {
56 private static final String SELECTED_KEY = "SELECTED";
57 private static final int ADD_REQUEST = 1;
58 private static final int EDIT_REQUEST = 2;
59
60 private List<VpnProfile> mVpnProfiles;
61 private VpnProfileDataSource mDataSource;
62 private VpnProfileAdapter mListAdapter;
63 private ListView mListView;
64 private OnVpnProfileSelectedListener mListener;
65 private HashSet<Integer> mSelected;
66 private boolean mReadOnly;
67
68 private BroadcastReceiver mProfilesChanged = new BroadcastReceiver()
69 {
70 @Override
71 public void onReceive(Context context, Intent intent)
72 {
73 long id, ids[];
74 if ((id = intent.getLongExtra(Constants.VPN_PROFILES_SINGLE, 0)) > 0)
75 {
76 VpnProfile profile = mDataSource.getVpnProfile(id);
77 if (profile != null)
78 { /* in case this was an edit, we remove it first */
79 mVpnProfiles.remove(profile);
80 mVpnProfiles.add(profile);
81 mListAdapter.notifyDataSetChanged();
82 }
83 }
84 else if ((ids = intent.getLongArrayExtra(Constants.VPN_PROFILES_MULTIPLE)) != null)
85 {
86 for (long i : ids)
87 {
88 Iterator<VpnProfile> profiles = mVpnProfiles.iterator();
89 while (profiles.hasNext())
90 {
91 VpnProfile profile = profiles.next();
92 if (profile.getId() == i)
93 {
94 profiles.remove();
95 break;
96 }
97 }
98 }
99 mListAdapter.notifyDataSetChanged();
100 }
101 }
102 };
103
104 /**
105 * The activity containing this fragment should implement this interface
106 */
107 public interface OnVpnProfileSelectedListener {
108 void onVpnProfileSelected(VpnProfile profile);
109 }
110
111 @Override
112 public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState)
113 {
114 super.onInflate(context, attrs, savedInstanceState);
115 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Fragment);
116 mReadOnly = a.getBoolean(R.styleable.Fragment_read_only, false);
117 a.recycle();
118 }
119
120 @Override
121 public View onCreateView(LayoutInflater inflater, ViewGroup container,
122 Bundle savedInstanceState)
123 {
124 View view = inflater.inflate(R.layout.profile_list_fragment, null);
125
126 mListView = view.findViewById(R.id.profile_list);
127 mListView.setAdapter(mListAdapter);
128 mListView.setEmptyView(view.findViewById(R.id.profile_list_empty));
129 mListView.setOnItemClickListener(mVpnProfileClicked);
130
131 if (!mReadOnly)
132 {
133 mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
134 mListView.setMultiChoiceModeListener(mVpnProfileSelected);
135 }
136 return view;
137 }
138
139 @Override
140 public void onCreate(Bundle savedInstanceState)
141 {
142 super.onCreate(savedInstanceState);
143
144 Bundle args = getArguments();
145 if (args != null)
146 {
147 mReadOnly = args.getBoolean("read_only", mReadOnly);
148 }
149
150 if (!mReadOnly)
151 {
152 setHasOptionsMenu(true);
153
154 ArrayList<Integer> selected = null;
155 if (savedInstanceState != null)
156 {
157 selected = savedInstanceState.getIntegerArrayList(SELECTED_KEY);
158 }
159 mSelected = selected != null ? new HashSet<>(selected) : new HashSet<>();
160 }
161
162 mDataSource = new VpnProfileDataSource(this.getActivity());
163 mDataSource.open();
164
165 /* cached list of profiles used as backend for the ListView */
166 mVpnProfiles = mDataSource.getAllVpnProfiles();
167
168 mListAdapter = new VpnProfileAdapter(getActivity(), R.layout.profile_list_item, mVpnProfiles);
169
170 IntentFilter profileChangesFilter = new IntentFilter(Constants.VPN_PROFILES_CHANGED);
171 LocalBroadcastManager.getInstance(getActivity()).registerReceiver(mProfilesChanged, profileChangesFilter);
172 }
173
174 @Override
175 public void onSaveInstanceState(Bundle outState)
176 {
177 super.onSaveInstanceState(outState);
178 outState.putIntegerArrayList(SELECTED_KEY, new ArrayList<>(mSelected));
179 }
180
181 @Override
182 public void onDestroy()
183 {
184 super.onDestroy();
185 mDataSource.close();
186 LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(mProfilesChanged);
187 }
188
189 @Override
190 public void onAttach(Context context)
191 {
192 super.onAttach(context);
193
194 if (context instanceof OnVpnProfileSelectedListener)
195 {
196 mListener = (OnVpnProfileSelectedListener)context;
197 }
198 }
199
200 @Override
201 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
202 {
203 inflater.inflate(R.menu.profile_list, menu);
204 }
205
206 @Override
207 public boolean onOptionsItemSelected(MenuItem item)
208 {
209 switch (item.getItemId())
210 {
211 case R.id.add_profile:
212 Intent connectionIntent = new Intent(getActivity(),
213 VpnProfileDetailActivity.class);
214 startActivityForResult(connectionIntent, ADD_REQUEST);
215 return true;
216 default:
217 return super.onOptionsItemSelected(item);
218 }
219 }
220
221 private final OnItemClickListener mVpnProfileClicked = new OnItemClickListener() {
222 @Override
223 public void onItemClick(AdapterView<?> a, View v, int position, long id)
224 {
225 if (mListener != null)
226 {
227 mListener.onVpnProfileSelected((VpnProfile)a.getItemAtPosition(position));
228 }
229 }
230 };
231
232 private final MultiChoiceModeListener mVpnProfileSelected = new MultiChoiceModeListener() {
233 private MenuItem mEditProfile;
234 private MenuItem mCopyProfile;
235
236 @Override
237 public boolean onPrepareActionMode(ActionMode mode, Menu menu)
238 {
239 mEditProfile.setEnabled(mSelected.size() == 1);
240 mCopyProfile.setEnabled(mEditProfile.isEnabled());
241 return true;
242 }
243
244 @Override
245 public void onDestroyActionMode(ActionMode mode)
246 {
247 mSelected.clear();
248 }
249
250 @Override
251 public boolean onCreateActionMode(ActionMode mode, Menu menu)
252 {
253 MenuInflater inflater = mode.getMenuInflater();
254 inflater.inflate(R.menu.profile_list_context, menu);
255 mEditProfile = menu.findItem(R.id.edit_profile);
256 mCopyProfile = menu.findItem(R.id.copy_profile);
257 mode.setTitle(R.string.select_profiles);
258 return true;
259 }
260
261 @Override
262 public boolean onActionItemClicked(ActionMode mode, MenuItem item)
263 {
264 switch (item.getItemId())
265 {
266 case R.id.edit_profile:
267 {
268 int position = mSelected.iterator().next();
269 VpnProfile profile = (VpnProfile)mListView.getItemAtPosition(position);
270 Intent connectionIntent = new Intent(getActivity(), VpnProfileDetailActivity.class);
271 connectionIntent.putExtra(VpnProfileDataSource.KEY_ID, profile.getId());
272 startActivityForResult(connectionIntent, EDIT_REQUEST);
273 break;
274 }
275 case R.id.copy_profile:
276 {
277 int position = mSelected.iterator().next();
278 VpnProfile profile = (VpnProfile)mListView.getItemAtPosition(position);
279 profile = profile.clone();
280 profile.setUUID(UUID.randomUUID());
281 profile.setName(String.format(getString(R.string.copied_name), profile.getName()));
282 mDataSource.insertProfile(profile);
283
284 Intent intent = new Intent(Constants.VPN_PROFILES_CHANGED);
285 intent.putExtra(Constants.VPN_PROFILES_SINGLE, profile.getId());
286 LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent);
287
288 Intent connectionIntent = new Intent(getActivity(), VpnProfileDetailActivity.class);
289 connectionIntent.putExtra(VpnProfileDataSource.KEY_ID, profile.getId());
290 startActivityForResult(connectionIntent, EDIT_REQUEST);
291 break;
292 }
293 case R.id.delete_profile:
294 {
295 ArrayList<VpnProfile> profiles = new ArrayList<>();
296 for (int position : mSelected)
297 {
298 profiles.add((VpnProfile)mListView.getItemAtPosition(position));
299 }
300 long ids[] = new long[profiles.size()];
301 for (int i = 0; i < profiles.size(); i++)
302 {
303 VpnProfile profile = profiles.get(i);
304 ids[i] = profile.getId();
305 mDataSource.deleteVpnProfile(profile);
306 }
307 Intent intent = new Intent(Constants.VPN_PROFILES_CHANGED);
308 intent.putExtra(Constants.VPN_PROFILES_MULTIPLE, ids);
309 LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent);
310 Toast.makeText(VpnProfileListFragment.this.getActivity(),
311 R.string.profiles_deleted, Toast.LENGTH_SHORT).show();
312 break;
313 }
314 default:
315 return false;
316 }
317 mode.finish();
318 return true;
319 }
320
321 @Override
322 public void onItemCheckedStateChanged(ActionMode mode, int position,
323 long id, boolean checked)
324 {
325 if (checked)
326 {
327 mSelected.add(position);
328 }
329 else
330 {
331 mSelected.remove(position);
332 }
333 final int checkedCount = mSelected.size();
334 switch (checkedCount)
335 {
336 case 0:
337 mode.setSubtitle(R.string.no_profile_selected);
338 break;
339 case 1:
340 mode.setSubtitle(R.string.one_profile_selected);
341 break;
342 default:
343 mode.setSubtitle(String.format(getString(R.string.x_profiles_selected), checkedCount));
344 break;
345 }
346 mode.invalidate();
347 }
348 };
349 }