]> git.ipfire.org Git - thirdparty/strongswan.git/blob - src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java
5effafdf10cbe5fb4db9dc9fb7b5660dbb565d58
[thirdparty/strongswan.git] / src / frontends / android / app / src / main / java / org / strongswan / android / logic / VpnStateService.java
1 /*
2 * Copyright (C) 2012-2017 Tobias Brunner
3 * HSR Hochschule fuer Technik Rapperswil
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 package org.strongswan.android.logic;
17
18 import android.app.Service;
19 import android.content.Context;
20 import android.content.Intent;
21 import android.os.Binder;
22 import android.os.Handler;
23 import android.os.IBinder;
24
25 import org.strongswan.android.R;
26 import org.strongswan.android.data.VpnProfile;
27 import org.strongswan.android.logic.imc.ImcState;
28 import org.strongswan.android.logic.imc.RemediationInstruction;
29
30 import java.util.Collections;
31 import java.util.HashSet;
32 import java.util.LinkedList;
33 import java.util.List;
34 import java.util.concurrent.Callable;
35
36 public class VpnStateService extends Service
37 {
38 private final HashSet<VpnStateListener> mListeners = new HashSet<VpnStateListener>();
39 private final IBinder mBinder = new LocalBinder();
40 private long mConnectionID = 0;
41 private Handler mHandler;
42 private VpnProfile mProfile;
43 private State mState = State.DISABLED;
44 private ErrorState mError = ErrorState.NO_ERROR;
45 private ImcState mImcState = ImcState.UNKNOWN;
46 private final LinkedList<RemediationInstruction> mRemediationInstructions = new LinkedList<RemediationInstruction>();
47
48 public enum State
49 {
50 DISABLED,
51 CONNECTING,
52 CONNECTED,
53 DISCONNECTING,
54 }
55
56 public enum ErrorState
57 {
58 NO_ERROR,
59 AUTH_FAILED,
60 PEER_AUTH_FAILED,
61 LOOKUP_FAILED,
62 UNREACHABLE,
63 GENERIC_ERROR,
64 }
65
66 /**
67 * Listener interface for bound clients that are interested in changes to
68 * this Service.
69 */
70 public interface VpnStateListener
71 {
72 public void stateChanged();
73 }
74
75 /**
76 * Simple Binder that allows to directly access this Service class itself
77 * after binding to it.
78 */
79 public class LocalBinder extends Binder
80 {
81 public VpnStateService getService()
82 {
83 return VpnStateService.this;
84 }
85 }
86
87 @Override
88 public void onCreate()
89 {
90 /* this handler allows us to notify listeners from the UI thread and
91 * not from the threads that actually report any state changes */
92 mHandler = new Handler();
93 }
94
95 @Override
96 public IBinder onBind(Intent intent)
97 {
98 return mBinder;
99 }
100
101 @Override
102 public void onDestroy()
103 {
104 }
105
106 /**
107 * Register a listener with this Service. We assume this is called from
108 * the main thread so no synchronization is happening.
109 *
110 * @param listener listener to register
111 */
112 public void registerListener(VpnStateListener listener)
113 {
114 mListeners.add(listener);
115 }
116
117 /**
118 * Unregister a listener from this Service.
119 *
120 * @param listener listener to unregister
121 */
122 public void unregisterListener(VpnStateListener listener)
123 {
124 mListeners.remove(listener);
125 }
126
127 /**
128 * Get the current VPN profile.
129 *
130 * @return profile
131 */
132 public VpnProfile getProfile()
133 { /* only updated from the main thread so no synchronization needed */
134 return mProfile;
135 }
136
137 /**
138 * Get the current connection ID. May be used to track which state
139 * changes have already been handled.
140 *
141 * Is increased when startConnection() is called.
142 *
143 * @return connection ID
144 */
145 public long getConnectionID()
146 { /* only updated from the main thread so no synchronization needed */
147 return mConnectionID;
148 }
149
150 /**
151 * Get the current state.
152 *
153 * @return state
154 */
155 public State getState()
156 { /* only updated from the main thread so no synchronization needed */
157 return mState;
158 }
159
160 /**
161 * Get the current error, if any.
162 *
163 * @return error
164 */
165 public ErrorState getErrorState()
166 { /* only updated from the main thread so no synchronization needed */
167 return mError;
168 }
169
170 /**
171 * Get a description of the current error, if any.
172 *
173 * @return error description text id
174 */
175 public int getErrorText()
176 {
177 switch (mError)
178 {
179 case AUTH_FAILED:
180 if (mImcState == ImcState.BLOCK)
181 {
182 return R.string.error_assessment_failed;
183 }
184 else
185 {
186 return R.string.error_auth_failed;
187 }
188 case PEER_AUTH_FAILED:
189 return R.string.error_peer_auth_failed;
190 case LOOKUP_FAILED:
191 return R.string.error_lookup_failed;
192 case UNREACHABLE:
193 return R.string.error_unreachable;
194 default:
195 return R.string.error_generic;
196 }
197 }
198
199 /**
200 * Get the current IMC state, if any.
201 *
202 * @return imc state
203 */
204 public ImcState getImcState()
205 { /* only updated from the main thread so no synchronization needed */
206 return mImcState;
207 }
208
209 /**
210 * Get the remediation instructions, if any.
211 *
212 * @return read-only list of instructions
213 */
214 public List<RemediationInstruction> getRemediationInstructions()
215 { /* only updated from the main thread so no synchronization needed */
216 return Collections.unmodifiableList(mRemediationInstructions);
217 }
218
219 /**
220 * Disconnect any existing connection and shutdown the daemon, the
221 * VpnService is not stopped but it is reset so new connections can be
222 * started.
223 */
224 public void disconnect()
225 {
226 /* as soon as the TUN device is created by calling establish() on the
227 * VpnService.Builder object the system binds to the service and keeps
228 * bound until the file descriptor of the TUN device is closed. thus
229 * calling stopService() here would not stop (destroy) the service yet,
230 * instead we call startService() with a specific action which shuts down
231 * the daemon (and closes the TUN device, if any) */
232 Context context = getApplicationContext();
233 Intent intent = new Intent(context, CharonVpnService.class);
234 intent.setAction(CharonVpnService.DISCONNECT_ACTION);
235 context.startService(intent);
236 }
237
238 /**
239 * Update state and notify all listeners about the change. By using a Handler
240 * this is done from the main UI thread and not the initial reporter thread.
241 * Also, in doing the actual state change from the main thread, listeners
242 * see all changes and none are skipped.
243 *
244 * @param change the state update to perform before notifying listeners, returns true if state changed
245 */
246 private void notifyListeners(final Callable<Boolean> change)
247 {
248 mHandler.post(new Runnable() {
249 @Override
250 public void run()
251 {
252 try
253 {
254 if (change.call())
255 { /* otherwise there is no need to notify the listeners */
256 for (VpnStateListener listener : mListeners)
257 {
258 listener.stateChanged();
259 }
260 }
261 }
262 catch (Exception e)
263 {
264 e.printStackTrace();
265 }
266 }
267 });
268 }
269
270 /**
271 * Called when a connection is started. Sets the currently active VPN
272 * profile, resets IMC and Error state variables, sets the State to
273 * CONNECTING, increases the connection ID, and notifies all listeners.
274 *
275 * May be called from threads other than the main thread.
276 *
277 * @param profile current profile
278 */
279 public void startConnection(final VpnProfile profile)
280 {
281 notifyListeners(new Callable<Boolean>() {
282 @Override
283 public Boolean call() throws Exception
284 {
285 VpnStateService.this.mConnectionID++;
286 VpnStateService.this.mProfile = profile;
287 VpnStateService.this.mState = State.CONNECTING;
288 VpnStateService.this.mError = ErrorState.NO_ERROR;
289 VpnStateService.this.mImcState = ImcState.UNKNOWN;
290 VpnStateService.this.mRemediationInstructions.clear();
291 return true;
292 }
293 });
294 }
295
296 /**
297 * Update the state and notify all listeners, if changed.
298 *
299 * May be called from threads other than the main thread.
300 *
301 * @param state new state
302 */
303 public void setState(final State state)
304 {
305 notifyListeners(new Callable<Boolean>() {
306 @Override
307 public Boolean call() throws Exception
308 {
309 if (VpnStateService.this.mState != state)
310 {
311 VpnStateService.this.mState = state;
312 return true;
313 }
314 return false;
315 }
316 });
317 }
318
319 /**
320 * Set the current error state and notify all listeners, if changed.
321 *
322 * May be called from threads other than the main thread.
323 *
324 * @param error error state
325 */
326 public void setError(final ErrorState error)
327 {
328 notifyListeners(new Callable<Boolean>() {
329 @Override
330 public Boolean call() throws Exception
331 {
332 if (VpnStateService.this.mError != error)
333 {
334 VpnStateService.this.mError = error;
335 return true;
336 }
337 return false;
338 }
339 });
340 }
341
342 /**
343 * Set the current IMC state and notify all listeners, if changed.
344 *
345 * Setting the state to UNKNOWN clears all remediation instructions.
346 *
347 * May be called from threads other than the main thread.
348 *
349 * @param state IMC state
350 */
351 public void setImcState(final ImcState state)
352 {
353 notifyListeners(new Callable<Boolean>() {
354 @Override
355 public Boolean call() throws Exception
356 {
357 if (state == ImcState.UNKNOWN)
358 {
359 VpnStateService.this.mRemediationInstructions.clear();
360 }
361 if (VpnStateService.this.mImcState != state)
362 {
363 VpnStateService.this.mImcState = state;
364 return true;
365 }
366 return false;
367 }
368 });
369 }
370
371 /**
372 * Add the given remediation instruction to the internal list. Listeners
373 * are not notified.
374 *
375 * Instructions are cleared if the IMC state is set to UNKNOWN.
376 *
377 * May be called from threads other than the main thread.
378 *
379 * @param instruction remediation instruction
380 */
381 public void addRemediationInstruction(final RemediationInstruction instruction)
382 {
383 mHandler.post(new Runnable() {
384 @Override
385 public void run()
386 {
387 VpnStateService.this.mRemediationInstructions.add(instruction);
388 }
389 });
390 }
391 }