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