]> git.ipfire.org Git - thirdparty/strongswan.git/blob - src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java
android: Add an automatic reconnect on errors
[thirdparty/strongswan.git] / src / frontends / android / app / src / main / java / org / strongswan / android / ui / VpnStateFragment.java
1 /*
2 * Copyright (C) 2012-2018 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.Intent;
24 import android.content.ServiceConnection;
25 import android.os.Bundle;
26 import android.os.IBinder;
27 import android.support.v4.app.Fragment;
28 import android.support.v4.content.ContextCompat;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.View.OnClickListener;
32 import android.view.ViewGroup;
33 import android.widget.Button;
34 import android.widget.LinearLayout;
35 import android.widget.ProgressBar;
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
45 public class VpnStateFragment extends Fragment implements VpnStateListener
46 {
47 private static final String KEY_ERROR_CONNECTION_ID = "error_connection_id";
48
49 private TextView mProfileNameView;
50 private TextView mProfileView;
51 private TextView mStateView;
52 private int mColorStateBase;
53 private int mColorStateError;
54 private int mColorStateSuccess;
55 private Button mActionButton;
56 private ProgressBar mProgress;
57 private LinearLayout mErrorView;
58 private TextView mErrorText;
59 private Button mErrorRetry;
60 private Button mShowLog;
61 private long mErrorConnectionID;
62 private VpnStateService mService;
63 private final ServiceConnection mServiceConnection = new ServiceConnection()
64 {
65 @Override
66 public void onServiceDisconnected(ComponentName name)
67 {
68 mService = null;
69 }
70
71 @Override
72 public void onServiceConnected(ComponentName name, IBinder service)
73 {
74 mService = ((VpnStateService.LocalBinder)service).getService();
75 mService.registerListener(VpnStateFragment.this);
76 updateView();
77 }
78 };
79 private OnClickListener mDisconnectListener = new OnClickListener()
80 {
81 @Override
82 public void onClick(View v)
83 {
84 if (mService != null)
85 {
86 mService.disconnect();
87 }
88 }
89 };
90
91 @Override
92 public void onCreate(Bundle savedInstanceState)
93 {
94 super.onCreate(savedInstanceState);
95
96 mColorStateError = ContextCompat.getColor(getActivity(), R.color.error_text);
97 mColorStateSuccess = ContextCompat.getColor(getActivity(), R.color.success_text);
98
99 /* bind to the service only seems to work from the ApplicationContext */
100 Context context = getActivity().getApplicationContext();
101 context.bindService(new Intent(context, VpnStateService.class),
102 mServiceConnection, Service.BIND_AUTO_CREATE);
103
104 mErrorConnectionID = 0;
105 if (savedInstanceState != null && savedInstanceState.containsKey(KEY_ERROR_CONNECTION_ID))
106 {
107 mErrorConnectionID = (Long)savedInstanceState.getSerializable(KEY_ERROR_CONNECTION_ID);
108 }
109 }
110
111 @Override
112 public void onSaveInstanceState(Bundle outState)
113 {
114 super.onSaveInstanceState(outState);
115
116 outState.putSerializable(KEY_ERROR_CONNECTION_ID, mErrorConnectionID);
117 }
118
119 @Override
120 public View onCreateView(LayoutInflater inflater, ViewGroup container,
121 Bundle savedInstanceState)
122 {
123 View view = inflater.inflate(R.layout.vpn_state_fragment, null);
124
125 mActionButton = (Button)view.findViewById(R.id.action);
126 mActionButton.setOnClickListener(v -> clearError());
127 enableActionButton(null);
128
129 mErrorView = view.findViewById(R.id.vpn_error);
130 mErrorText = view.findViewById(R.id.vpn_error_text);
131 mErrorRetry = view.findViewById(R.id.retry);
132 mShowLog = view.findViewById(R.id.show_log);
133 mProgress = (ProgressBar)view.findViewById(R.id.progress);
134 mStateView = (TextView)view.findViewById(R.id.vpn_state);
135 mColorStateBase = mStateView.getCurrentTextColor();
136 mProfileView = (TextView)view.findViewById(R.id.vpn_profile_label);
137 mProfileNameView = (TextView)view.findViewById(R.id.vpn_profile_name);
138
139 mErrorRetry.setOnClickListener(v -> {
140 if (mService != null)
141 {
142 mService.reconnect();
143 }
144 });
145 mShowLog.setOnClickListener(v -> {
146 Intent intent = new Intent(getActivity(), LogActivity.class);
147 startActivity(intent);
148 });
149
150 return view;
151 }
152
153 @Override
154 public void onStart()
155 {
156 super.onStart();
157 if (mService != null)
158 {
159 mService.registerListener(this);
160 updateView();
161 }
162 }
163
164 @Override
165 public void onStop()
166 {
167 super.onStop();
168 if (mService != null)
169 {
170 mService.unregisterListener(this);
171 }
172 }
173
174 @Override
175 public void onDestroy()
176 {
177 super.onDestroy();
178 if (mService != null)
179 {
180 getActivity().getApplicationContext().unbindService(mServiceConnection);
181 }
182 }
183
184 @Override
185 public void stateChanged()
186 {
187 updateView();
188 }
189
190 public void updateView()
191 {
192 long connectionID = mService.getConnectionID();
193 VpnProfile profile = mService.getProfile();
194 State state = mService.getState();
195 ErrorState error = mService.getErrorState();
196 String name = "";
197
198 if (getActivity() == null)
199 {
200 return;
201 }
202
203 if (profile != null)
204 {
205 name = profile.getName();
206 }
207
208 if (reportError(connectionID, name, error))
209 {
210 return;
211 }
212
213 mProfileNameView.setText(name);
214 mProgress.setIndeterminate(true);
215
216 switch (state)
217 {
218 case DISABLED:
219 showProfile(false);
220 mProgress.setVisibility(View.GONE);
221 enableActionButton(null);
222 mStateView.setText(R.string.state_disabled);
223 mStateView.setTextColor(mColorStateBase);
224 break;
225 case CONNECTING:
226 showProfile(true);
227 mProgress.setVisibility(View.VISIBLE);
228 enableActionButton(getString(android.R.string.cancel));
229 mStateView.setText(R.string.state_connecting);
230 mStateView.setTextColor(mColorStateBase);
231 break;
232 case CONNECTED:
233 showProfile(true);
234 mProgress.setVisibility(View.GONE);
235 enableActionButton(getString(R.string.disconnect));
236 mStateView.setText(R.string.state_connected);
237 mStateView.setTextColor(mColorStateSuccess);
238 break;
239 case DISCONNECTING:
240 showProfile(true);
241 mProgress.setVisibility(View.VISIBLE);
242 enableActionButton(null);
243 mStateView.setText(R.string.state_disconnecting);
244 mStateView.setTextColor(mColorStateBase);
245 break;
246 }
247 }
248
249 private boolean reportError(long connectionID, String name, ErrorState error)
250 {
251 if (error == ErrorState.NO_ERROR)
252 {
253 mErrorView.setVisibility(View.GONE);
254 return false;
255 }
256 mErrorConnectionID = connectionID;
257 mProfileNameView.setText(name);
258 showProfile(true);
259 mStateView.setText(R.string.state_error);
260 mStateView.setTextColor(mColorStateError);
261 enableActionButton(getString(android.R.string.cancel));
262
263 int retry = mService.getRetryIn();
264 if (retry > 0)
265 {
266 mProgress.setIndeterminate(false);
267 mProgress.setMax(mService.getRetryTimeout());
268 mProgress.setProgress(retry);
269 mProgress.setVisibility(View.VISIBLE);
270 mStateView.setText(getResources().getQuantityString(R.plurals.retry_in, retry, retry));
271 }
272 else if (mService.getRetryTimeout() <= 0)
273 {
274 mProgress.setVisibility(View.GONE);
275 }
276
277 String text = getString(R.string.error_format, getString(mService.getErrorText()));
278 mErrorText.setText(text);
279 mErrorView.setVisibility(View.VISIBLE);
280 return true;
281 }
282
283 private void showProfile(boolean show)
284 {
285 mProfileView.setVisibility(show ? View.VISIBLE : View.GONE);
286 mProfileNameView.setVisibility(show ? View.VISIBLE : View.GONE);
287 }
288
289 private void enableActionButton(String text)
290 {
291 mActionButton.setText(text);
292 mActionButton.setEnabled(text != null);
293 mActionButton.setVisibility(text != null ? View.VISIBLE : View.GONE);
294 }
295
296 private void clearError()
297 {
298 if (mService != null)
299 {
300 mService.disconnect();
301 if (mService.getConnectionID() == mErrorConnectionID)
302 {
303 mService.setError(ErrorState.NO_ERROR);
304 }
305 }
306 }
307 }