88f8df377f2e24880f4eac87ddc9b60423e0fa1f
[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 mService.registerListener(this);
148 updateView();
149 }
150 }
151
152 @Override
153 public void onStop()
154 {
155 super.onStop();
156 if (mService != null)
157 {
158 mService.unregisterListener(this);
159 }
160 hideErrorDialog();
161 hideProgressDialog();
162 }
163
164 @Override
165 public void onDestroy()
166 {
167 super.onDestroy();
168 if (mService != null)
169 {
170 getActivity().getApplicationContext().unbindService(mServiceConnection);
171 }
172 }
173
174 @Override
175 public void stateChanged()
176 {
177 updateView();
178 }
179
180 public void updateView()
181 {
182 State state = mService.getState();
183 ErrorState error = ErrorState.NO_ERROR;
184 ImcState imcState = ImcState.UNKNOWN;
185 String name = "", gateway = "";
186
187 if (state != State.DISABLED)
188 {
189 VpnProfile profile = mService.getProfile();
190 if (profile != null)
191 {
192 name = profile.getName();
193 gateway = profile.getGateway();
194 }
195 error = mService.getErrorState();
196 imcState = mService.getImcState();
197 }
198
199 if (reportError(name, state, error, imcState))
200 {
201 return;
202 }
203
204 if (state == mState)
205 { /* avoid unnecessary updates */
206 return;
207 }
208
209 hideProgressDialog();
210 enableActionButton(false);
211 mProfileNameView.setText(name);
212 mState = state;
213
214 switch (state)
215 {
216 case DISABLED:
217 showProfile(false);
218 mStateView.setText(R.string.state_disabled);
219 mStateView.setTextColor(stateBaseColor);
220 break;
221 case CONNECTING:
222 showProfile(true);
223 showConnectDialog(name, gateway);
224 mStateView.setText(R.string.state_connecting);
225 mStateView.setTextColor(stateBaseColor);
226 break;
227 case CONNECTED:
228 showProfile(true);
229 enableActionButton(true);
230 mStateView.setText(R.string.state_connected);
231 mStateView.setTextColor(getResources().getColor(R.color.success_text));
232 break;
233 case DISCONNECTING:
234 showProfile(true);
235 showDisconnectDialog(name);
236 mStateView.setText(R.string.state_disconnecting);
237 mStateView.setTextColor(stateBaseColor);
238 break;
239 }
240 }
241
242 private boolean reportError(String name, State state, ErrorState error, ImcState imcState)
243 {
244 if (mError != ErrorState.NO_ERROR)
245 { /* we are currently reporting an error which was not yet dismissed */
246 error = mError;
247 imcState = mImcState;
248 name = mErrorProfileName;
249 }
250 else if (error != ErrorState.NO_ERROR && (state == State.CONNECTING || state == State.CONNECTED))
251 { /* while initiating we report errors */
252 mError = error;
253 mImcState = imcState;
254 mErrorProfileName = name;
255 }
256 else
257 { /* ignore all other errors */
258 error = ErrorState.NO_ERROR;
259 }
260 if (error == ErrorState.NO_ERROR)
261 {
262 hideErrorDialog();
263 return false;
264 }
265 else if (mErrorDialog != null)
266 { /* we already show the dialog */
267 return true;
268 }
269 hideProgressDialog();
270 mProfileNameView.setText(name);
271 showProfile(true);
272 enableActionButton(false);
273 mStateView.setText(R.string.state_error);
274 mStateView.setTextColor(getResources().getColor(R.color.error_text));
275 switch (error)
276 {
277 case AUTH_FAILED:
278 if (imcState == ImcState.BLOCK)
279 {
280 showErrorDialog(R.string.error_assessment_failed);
281 }
282 else
283 {
284 showErrorDialog(R.string.error_auth_failed);
285 }
286 break;
287 case PEER_AUTH_FAILED:
288 showErrorDialog(R.string.error_peer_auth_failed);
289 break;
290 case LOOKUP_FAILED:
291 showErrorDialog(R.string.error_lookup_failed);
292 break;
293 case UNREACHABLE:
294 showErrorDialog(R.string.error_unreachable);
295 break;
296 default:
297 showErrorDialog(R.string.error_generic);
298 break;
299 }
300 return true;
301 }
302
303 private void showProfile(boolean show)
304 {
305 mProfileView.setVisibility(show ? View.VISIBLE : View.GONE);
306 mProfileNameView.setVisibility(show ? View.VISIBLE : View.GONE);
307 }
308
309 private void enableActionButton(boolean enable)
310 {
311 mActionButton.setEnabled(enable);
312 mActionButton.setVisibility(enable ? View.VISIBLE : View.GONE);
313 }
314
315 private void hideProgressDialog()
316 {
317 if (mProgressDialog != null)
318 {
319 mProgressDialog.dismiss();
320 mProgressDialog = null;
321 }
322 }
323
324 private void hideErrorDialog()
325 {
326 if (mErrorDialog != null)
327 {
328 mErrorDialog.dismiss();
329 mErrorDialog = null;
330 }
331 }
332
333 private void clearError()
334 {
335 mError = ErrorState.NO_ERROR;
336 mImcState = ImcState.UNKNOWN;
337 updateView();
338 }
339
340 private void showConnectDialog(String profile, String gateway)
341 {
342 mProgressDialog = new ProgressDialog(getActivity());
343 mProgressDialog.setTitle(String.format(getString(R.string.connecting_title), profile));
344 mProgressDialog.setMessage(String.format(getString(R.string.connecting_message), gateway));
345 mProgressDialog.setIndeterminate(true);
346 mProgressDialog.setCancelable(false);
347 mProgressDialog.setButton(getString(android.R.string.cancel),
348 new DialogInterface.OnClickListener()
349 {
350 @Override
351 public void onClick(DialogInterface dialog, int which)
352 {
353 if (mService != null)
354 {
355 mService.disconnect();
356 }
357 }
358 });
359 mProgressDialog.show();
360 }
361
362 private void showDisconnectDialog(String profile)
363 {
364 mProgressDialog = new ProgressDialog(getActivity());
365 mProgressDialog.setMessage(getString(R.string.state_disconnecting));
366 mProgressDialog.setIndeterminate(true);
367 mProgressDialog.setCancelable(false);
368 mProgressDialog.show();
369 }
370
371 private void showErrorDialog(int textid)
372 {
373 final List<RemediationInstruction> instructions = mService.getRemediationInstructions();
374 final boolean show_instructions = mImcState == ImcState.BLOCK && !instructions.isEmpty();
375 int text = show_instructions ? R.string.show_remediation_instructions : R.string.show_log;
376
377 mErrorDialog = new AlertDialog.Builder(getActivity())
378 .setMessage(getString(R.string.error_introduction) + " " + getString(textid))
379 .setCancelable(false)
380 .setNeutralButton(text, new DialogInterface.OnClickListener() {
381 @Override
382 public void onClick(DialogInterface dialog, int which)
383 {
384 clearError();
385 dialog.dismiss();
386 Intent intent;
387 if (show_instructions)
388 {
389 intent = new Intent(getActivity(), RemediationInstructionsActivity.class);
390 intent.putParcelableArrayListExtra(RemediationInstructionsFragment.EXTRA_REMEDIATION_INSTRUCTIONS,
391 new ArrayList<RemediationInstruction>(instructions));
392 }
393 else
394 {
395 intent = new Intent(getActivity(), LogActivity.class);
396 }
397 startActivity(intent);
398 }
399 })
400 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
401 @Override
402 public void onClick(DialogInterface dialog, int id)
403 {
404 clearError();
405 dialog.dismiss();
406 }
407 }).create();
408 mErrorDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
409 @Override
410 public void onDismiss(DialogInterface dialog)
411 {
412 mErrorDialog = null;
413 }
414 });
415 mErrorDialog.show();
416 }
417 }