android: Avoid ProgressDialogs in VPN state fragment
[strongswan.git] / src / frontends / android / app / src / main / java / org / strongswan / android / ui / VpnStateFragment.java
1 /*
2 * Copyright (C) 2012-2016 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.app.Service;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.content.Intent;
25 import android.content.ServiceConnection;
26 import android.os.Bundle;
27 import android.os.IBinder;
28 import android.support.v4.app.Fragment;
29 import android.support.v4.content.ContextCompat;
30 import android.support.v7.app.AlertDialog;
31 import android.view.LayoutInflater;
32 import android.view.View;
33 import android.view.View.OnClickListener;
34 import android.view.ViewGroup;
35 import android.widget.Button;
36 import android.widget.ProgressBar;
37 import android.widget.TextView;
38
39 import org.strongswan.android.R;
40 import org.strongswan.android.data.VpnProfile;
41 import org.strongswan.android.logic.VpnStateService;
42 import org.strongswan.android.logic.VpnStateService.ErrorState;
43 import org.strongswan.android.logic.VpnStateService.State;
44 import org.strongswan.android.logic.VpnStateService.VpnStateListener;
45 import org.strongswan.android.logic.imc.ImcState;
46 import org.strongswan.android.logic.imc.RemediationInstruction;
47
48 import java.util.ArrayList;
49 import java.util.List;
50
51 public class VpnStateFragment extends Fragment implements VpnStateListener
52 {
53 private static final String KEY_ERROR_CONNECTION_ID = "error_connection_id";
54 private static final String KEY_DISMISSED_CONNECTION_ID = "dismissed_connection_id";
55
56 private TextView mProfileNameView;
57 private TextView mProfileView;
58 private TextView mStateView;
59 private int mColorStateBase;
60 private int mColorStateError;
61 private int mColorStateSuccess;
62 private Button mActionButton;
63 private ProgressBar mProgress;
64 private AlertDialog mErrorDialog;
65 private long mErrorConnectionID;
66 private long mDismissedConnectionID;
67 private VpnStateService mService;
68 private final ServiceConnection mServiceConnection = new ServiceConnection()
69 {
70 @Override
71 public void onServiceDisconnected(ComponentName name)
72 {
73 mService = null;
74 }
75
76 @Override
77 public void onServiceConnected(ComponentName name, IBinder service)
78 {
79 mService = ((VpnStateService.LocalBinder)service).getService();
80 mService.registerListener(VpnStateFragment.this);
81 updateView();
82 }
83 };
84
85 @Override
86 public void onCreate(Bundle savedInstanceState)
87 {
88 super.onCreate(savedInstanceState);
89
90 mColorStateError = ContextCompat.getColor(getActivity(), R.color.error_text);
91 mColorStateSuccess = ContextCompat.getColor(getActivity(), R.color.success_text);
92
93 /* bind to the service only seems to work from the ApplicationContext */
94 Context context = getActivity().getApplicationContext();
95 context.bindService(new Intent(context, VpnStateService.class),
96 mServiceConnection, Service.BIND_AUTO_CREATE);
97
98 mErrorConnectionID = 0;
99 mDismissedConnectionID = 0;
100 if (savedInstanceState != null && savedInstanceState.containsKey(KEY_ERROR_CONNECTION_ID))
101 {
102 mErrorConnectionID = (Long)savedInstanceState.getSerializable(KEY_ERROR_CONNECTION_ID);
103 mDismissedConnectionID = (Long)savedInstanceState.getSerializable(KEY_DISMISSED_CONNECTION_ID);
104 }
105 }
106
107 @Override
108 public void onSaveInstanceState(Bundle outState)
109 {
110 super.onSaveInstanceState(outState);
111
112 outState.putSerializable(KEY_ERROR_CONNECTION_ID, mErrorConnectionID);
113 outState.putSerializable(KEY_DISMISSED_CONNECTION_ID, mDismissedConnectionID);
114 }
115
116 @Override
117 public View onCreateView(LayoutInflater inflater, ViewGroup container,
118 Bundle savedInstanceState)
119 {
120 View view = inflater.inflate(R.layout.vpn_state_fragment, null);
121
122 mActionButton = (Button)view.findViewById(R.id.action);
123 mActionButton.setOnClickListener(new OnClickListener()
124 {
125 @Override
126 public void onClick(View v)
127 {
128 if (mService != null)
129 {
130 mService.disconnect();
131 }
132 }
133 });
134 enableActionButton(null);
135
136 mProgress = (ProgressBar)view.findViewById(R.id.progress);
137 mStateView = (TextView)view.findViewById(R.id.vpn_state);
138 mColorStateBase = mStateView.getCurrentTextColor();
139 mProfileView = (TextView)view.findViewById(R.id.vpn_profile_label);
140 mProfileNameView = (TextView)view.findViewById(R.id.vpn_profile_name);
141
142 return view;
143 }
144
145 @Override
146 public void onStart()
147 {
148 super.onStart();
149 if (mService != null)
150 {
151 mService.registerListener(this);
152 updateView();
153 }
154 }
155
156 @Override
157 public void onStop()
158 {
159 super.onStop();
160 if (mService != null)
161 {
162 mService.unregisterListener(this);
163 }
164 hideErrorDialog();
165 }
166
167 @Override
168 public void onDestroy()
169 {
170 super.onDestroy();
171 if (mService != null)
172 {
173 getActivity().getApplicationContext().unbindService(mServiceConnection);
174 }
175 }
176
177 @Override
178 public void stateChanged()
179 {
180 updateView();
181 }
182
183 public void updateView()
184 {
185 long connectionID = mService.getConnectionID();
186 VpnProfile profile = mService.getProfile();
187 State state = mService.getState();
188 ErrorState error = mService.getErrorState();
189 ImcState imcState = mService.getImcState();
190 String name = "", gateway = "";
191
192 if (profile != null)
193 {
194 name = profile.getName();
195 gateway = profile.getGateway();
196 }
197
198 if (reportError(connectionID, name, error, imcState))
199 {
200 return;
201 }
202
203 mProfileNameView.setText(name);
204
205 switch (state)
206 {
207 case DISABLED:
208 showProfile(false);
209 mProgress.setVisibility(View.GONE);
210 enableActionButton(null);
211 mStateView.setText(R.string.state_disabled);
212 mStateView.setTextColor(mColorStateBase);
213 break;
214 case CONNECTING:
215 showProfile(true);
216 mProgress.setVisibility(View.VISIBLE);
217 enableActionButton(getString(android.R.string.cancel));
218 mStateView.setText(R.string.state_connecting);
219 mStateView.setTextColor(mColorStateBase);
220 break;
221 case CONNECTED:
222 showProfile(true);
223 mProgress.setVisibility(View.GONE);
224 enableActionButton(getString(R.string.disconnect));
225 mStateView.setText(R.string.state_connected);
226 mStateView.setTextColor(mColorStateSuccess);
227 break;
228 case DISCONNECTING:
229 showProfile(true);
230 mProgress.setVisibility(View.VISIBLE);
231 enableActionButton(null);
232 mStateView.setText(R.string.state_disconnecting);
233 mStateView.setTextColor(mColorStateBase);
234 break;
235 }
236 }
237
238 private boolean reportError(long connectionID, String name, ErrorState error, ImcState imcState)
239 {
240 if (connectionID > mDismissedConnectionID)
241 { /* report error if it hasn't been dismissed yet */
242 mErrorConnectionID = connectionID;
243 }
244 else
245 { /* ignore all other errors */
246 error = ErrorState.NO_ERROR;
247 }
248 if (error == ErrorState.NO_ERROR)
249 {
250 hideErrorDialog();
251 return false;
252 }
253 else if (mErrorDialog != null)
254 { /* we already show the dialog */
255 return true;
256 }
257 mProfileNameView.setText(name);
258 showProfile(true);
259 mProgress.setVisibility(View.GONE);
260 enableActionButton(null);
261 mStateView.setText(R.string.state_error);
262 mStateView.setTextColor(mColorStateError);
263 switch (error)
264 {
265 case AUTH_FAILED:
266 if (imcState == ImcState.BLOCK)
267 {
268 showErrorDialog(R.string.error_assessment_failed);
269 }
270 else
271 {
272 showErrorDialog(R.string.error_auth_failed);
273 }
274 break;
275 case PEER_AUTH_FAILED:
276 showErrorDialog(R.string.error_peer_auth_failed);
277 break;
278 case LOOKUP_FAILED:
279 showErrorDialog(R.string.error_lookup_failed);
280 break;
281 case UNREACHABLE:
282 showErrorDialog(R.string.error_unreachable);
283 break;
284 default:
285 showErrorDialog(R.string.error_generic);
286 break;
287 }
288 return true;
289 }
290
291 private void showProfile(boolean show)
292 {
293 mProfileView.setVisibility(show ? View.VISIBLE : View.GONE);
294 mProfileNameView.setVisibility(show ? View.VISIBLE : View.GONE);
295 }
296
297 private void enableActionButton(String text)
298 {
299 mActionButton.setText(text);
300 mActionButton.setEnabled(text != null);
301 mActionButton.setVisibility(text != null ? View.VISIBLE : View.GONE);
302 }
303
304 private void hideErrorDialog()
305 {
306 if (mErrorDialog != null)
307 {
308 mErrorDialog.dismiss();
309 mErrorDialog = null;
310 }
311 }
312
313 private void clearError()
314 {
315 if (mService != null)
316 {
317 mService.disconnect();
318 }
319 mDismissedConnectionID = mErrorConnectionID;
320 updateView();
321 }
322
323 private void showErrorDialog(int textid)
324 {
325 final List<RemediationInstruction> instructions = mService.getRemediationInstructions();
326 final boolean show_instructions = mService.getImcState() == ImcState.BLOCK && !instructions.isEmpty();
327 int text = show_instructions ? R.string.show_remediation_instructions : R.string.show_log;
328
329 mErrorDialog = new AlertDialog.Builder(getActivity())
330 .setMessage(getString(R.string.error_introduction) + " " + getString(textid))
331 .setCancelable(false)
332 .setNeutralButton(text, new DialogInterface.OnClickListener()
333 {
334 @Override
335 public void onClick(DialogInterface dialog, int which)
336 {
337 clearError();
338 dialog.dismiss();
339 Intent intent;
340 if (show_instructions)
341 {
342 intent = new Intent(getActivity(), RemediationInstructionsActivity.class);
343 intent.putParcelableArrayListExtra(RemediationInstructionsFragment.EXTRA_REMEDIATION_INSTRUCTIONS,
344 new ArrayList<RemediationInstruction>(instructions));
345 }
346 else
347 {
348 intent = new Intent(getActivity(), LogActivity.class);
349 }
350 startActivity(intent);
351 }
352 })
353 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
354 {
355 @Override
356 public void onClick(DialogInterface dialog, int id)
357 {
358 clearError();
359 dialog.dismiss();
360 }
361 }).create();
362 mErrorDialog.setOnDismissListener(new DialogInterface.OnDismissListener()
363 {
364 @Override
365 public void onDismiss(DialogInterface dialog)
366 {
367 mErrorDialog = null;
368 }
369 });
370 mErrorDialog.show();
371 }
372 }