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