2c530bad4720fe0021153aca548d780387ccb28c
[strongswan.git] / src / frontends / android / src / org / strongswan / android / logic / VpnStateService.java
1 /*
2 * Copyright (C) 2012-2013 Tobias Brunner
3 * 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 java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.LinkedList;
21 import java.util.List;
22 import java.util.concurrent.Callable;
23
24 import org.strongswan.android.data.VpnProfile;
25 import org.strongswan.android.logic.imc.ImcState;
26 import org.strongswan.android.logic.imc.RemediationInstruction;
27
28 import android.app.Service;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.os.Binder;
32 import android.os.Handler;
33 import android.os.IBinder;
34
35 public class VpnStateService extends Service
36 {
37 private final List<VpnStateListener> mListeners = new ArrayList<VpnStateListener>();
38 private final IBinder mBinder = new LocalBinder();
39 private Handler mHandler;
40 private VpnProfile mProfile;
41 private State mState = State.DISABLED;
42 private ErrorState mError = ErrorState.NO_ERROR;
43 private ImcState mImcState = ImcState.UNKNOWN;
44 private final LinkedList<RemediationInstruction> mRemediationInstructions = new LinkedList<RemediationInstruction>();
45
46 public enum State
47 {
48 DISABLED,
49 CONNECTING,
50 CONNECTED,
51 DISCONNECTING,
52 }
53
54 public enum ErrorState
55 {
56 NO_ERROR,
57 AUTH_FAILED,
58 PEER_AUTH_FAILED,
59 LOOKUP_FAILED,
60 UNREACHABLE,
61 GENERIC_ERROR,
62 }
63
64 /**
65 * Listener interface for bound clients that are interested in changes to
66 * this Service.
67 */
68 public interface VpnStateListener
69 {
70 public void stateChanged();
71 }
72
73 /**
74 * Simple Binder that allows to directly access this Service class itself
75 * after binding to it.
76 */
77 public class LocalBinder extends Binder
78 {
79 public VpnStateService getService()
80 {
81 return VpnStateService.this;
82 }
83 }
84
85 @Override
86 public void onCreate()
87 {
88 /* this handler allows us to notify listeners from the UI thread and
89 * not from the threads that actually report any state changes */
90 mHandler = new Handler();
91 }
92
93 @Override
94 public IBinder onBind(Intent intent)
95 {
96 return mBinder;
97 }
98
99 @Override
100 public void onDestroy()
101 {
102 }
103
104 /**
105 * Register a listener with this Service. We assume this is called from
106 * the main thread so no synchronization is happening.
107 *
108 * @param listener listener to register
109 */
110 public void registerListener(VpnStateListener listener)
111 {
112 mListeners.add(listener);
113 }
114
115 /**
116 * Unregister a listener from this Service.
117 *
118 * @param listener listener to unregister
119 */
120 public void unregisterListener(VpnStateListener listener)
121 {
122 mListeners.remove(listener);
123 }
124
125 /**
126 * Get the current VPN profile.
127 *
128 * @return profile
129 */
130 public VpnProfile getProfile()
131 { /* only updated from the main thread so no synchronization needed */
132 return mProfile;
133 }
134
135 /**
136 * Get the current state.
137 *
138 * @return state
139 */
140 public State getState()
141 { /* only updated from the main thread so no synchronization needed */
142 return mState;
143 }
144
145 /**
146 * Get the current error, if any.
147 *
148 * @return error
149 */
150 public ErrorState getErrorState()
151 { /* only updated from the main thread so no synchronization needed */
152 return mError;
153 }
154
155 /**
156 * Get the current IMC state, if any.
157 *
158 * @return imc state
159 */
160 public ImcState getImcState()
161 { /* only updated from the main thread so no synchronization needed */
162 return mImcState;
163 }
164
165 /**
166 * Get the remediation instructions, if any.
167 *
168 * @return read-only list of instructions
169 */
170 public List<RemediationInstruction> getRemediationInstructions()
171 { /* only updated from the main thread so no synchronization needed */
172 return Collections.unmodifiableList(mRemediationInstructions);
173 }
174
175 /**
176 * Disconnect any existing connection and shutdown the daemon, the
177 * VpnService is not stopped but it is reset so new connections can be
178 * started.
179 */
180 public void disconnect()
181 {
182 /* as soon as the TUN device is created by calling establish() on the
183 * VpnService.Builder object the system binds to the service and keeps
184 * bound until the file descriptor of the TUN device is closed. thus
185 * calling stopService() here would not stop (destroy) the service yet,
186 * instead we call startService() with an empty Intent which shuts down
187 * the daemon (and closes the TUN device, if any) */
188 Context context = getApplicationContext();
189 Intent intent = new Intent(context, CharonVpnService.class);
190 context.startService(intent);
191 }
192
193 /**
194 * Update state and notify all listeners about the change. By using a Handler
195 * this is done from the main UI thread and not the initial reporter thread.
196 * Also, in doing the actual state change from the main thread, listeners
197 * see all changes and none are skipped.
198 *
199 * @param change the state update to perform before notifying listeners, returns true if state changed
200 */
201 private void notifyListeners(final Callable<Boolean> change)
202 {
203 mHandler.post(new Runnable() {
204 @Override
205 public void run()
206 {
207 try
208 {
209 if (change.call())
210 { /* otherwise there is no need to notify the listeners */
211 for (VpnStateListener listener : mListeners)
212 {
213 listener.stateChanged();
214 }
215 }
216 }
217 catch (Exception e)
218 {
219 e.printStackTrace();
220 }
221 }
222 });
223 }
224
225 /**
226 * Set the VPN profile currently active. Listeners are not notified.
227 *
228 * May be called from threads other than the main thread.
229 *
230 * @param profile current profile
231 */
232 public void setProfile(final VpnProfile profile)
233 {
234 /* even though we don't notify the listeners the update is done from the
235 * same handler so updates are predictable for listeners */
236 mHandler.post(new Runnable() {
237 @Override
238 public void run()
239 {
240 VpnStateService.this.mProfile = profile;
241 }
242 });
243 }
244
245 /**
246 * Update the state and notify all listeners, if changed.
247 *
248 * May be called from threads other than the main thread.
249 *
250 * @param state new state
251 */
252 public void setState(final State state)
253 {
254 notifyListeners(new Callable<Boolean>() {
255 @Override
256 public Boolean call() throws Exception
257 {
258 if (VpnStateService.this.mState != state)
259 {
260 VpnStateService.this.mState = state;
261 return true;
262 }
263 return false;
264 }
265 });
266 }
267
268 /**
269 * Set the current error state and notify all listeners, if changed.
270 *
271 * May be called from threads other than the main thread.
272 *
273 * @param error error state
274 */
275 public void setError(final ErrorState error)
276 {
277 notifyListeners(new Callable<Boolean>() {
278 @Override
279 public Boolean call() throws Exception
280 {
281 if (VpnStateService.this.mError != error)
282 {
283 VpnStateService.this.mError = error;
284 return true;
285 }
286 return false;
287 }
288 });
289 }
290
291 /**
292 * Set the current IMC state and notify all listeners, if changed.
293 *
294 * Setting the state to UNKNOWN clears all remediation instructions.
295 *
296 * May be called from threads other than the main thread.
297 *
298 * @param error error state
299 */
300 public void setImcState(final ImcState state)
301 {
302 notifyListeners(new Callable<Boolean>() {
303 @Override
304 public Boolean call() throws Exception
305 {
306 if (state == ImcState.UNKNOWN)
307 {
308 VpnStateService.this.mRemediationInstructions.clear();
309 }
310 if (VpnStateService.this.mImcState != state)
311 {
312 VpnStateService.this.mImcState = state;
313 return true;
314 }
315 return false;
316 }
317 });
318 }
319
320 /**
321 * Add the given remediation instruction to the internal list. Listeners
322 * are not notified.
323 *
324 * Instructions are cleared if the IMC state is set to UNKNOWN.
325 *
326 * May be called from threads other than the main thread.
327 *
328 * @param instruction remediation instruction
329 */
330 public void addRemediationInstruction(final RemediationInstruction instruction)
331 {
332 mHandler.post(new Runnable() {
333 @Override
334 public void run()
335 {
336 VpnStateService.this.mRemediationInstructions.add(instruction);
337 }
338 });
339 }
340 }