2 * Copyright (C) 2012-2017 Tobias Brunner
3 * HSR Hochschule fuer Technik Rapperswil
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>.
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
16 package org
.strongswan
.android
.logic
;
18 import android
.app
.Service
;
19 import android
.content
.Context
;
20 import android
.content
.Intent
;
21 import android
.os
.Binder
;
22 import android
.os
.Bundle
;
23 import android
.os
.Handler
;
24 import android
.os
.IBinder
;
26 import org
.strongswan
.android
.R
;
27 import org
.strongswan
.android
.data
.VpnProfile
;
28 import org
.strongswan
.android
.data
.VpnProfileDataSource
;
29 import org
.strongswan
.android
.logic
.imc
.ImcState
;
30 import org
.strongswan
.android
.logic
.imc
.RemediationInstruction
;
32 import java
.util
.Collections
;
33 import java
.util
.HashSet
;
34 import java
.util
.LinkedList
;
35 import java
.util
.List
;
36 import java
.util
.concurrent
.Callable
;
38 public class VpnStateService
extends Service
40 private final HashSet
<VpnStateListener
> mListeners
= new HashSet
<VpnStateListener
>();
41 private final IBinder mBinder
= new LocalBinder();
42 private long mConnectionID
= 0;
43 private Handler mHandler
;
44 private VpnProfile mProfile
;
45 private State mState
= State
.DISABLED
;
46 private ErrorState mError
= ErrorState
.NO_ERROR
;
47 private ImcState mImcState
= ImcState
.UNKNOWN
;
48 private final LinkedList
<RemediationInstruction
> mRemediationInstructions
= new LinkedList
<RemediationInstruction
>();
58 public enum ErrorState
67 CERTIFICATE_UNAVAILABLE
,
71 * Listener interface for bound clients that are interested in changes to
74 public interface VpnStateListener
76 public void stateChanged();
80 * Simple Binder that allows to directly access this Service class itself
81 * after binding to it.
83 public class LocalBinder
extends Binder
85 public VpnStateService
getService()
87 return VpnStateService
.this;
92 public void onCreate()
94 /* this handler allows us to notify listeners from the UI thread and
95 * not from the threads that actually report any state changes */
96 mHandler
= new Handler();
100 public IBinder
onBind(Intent intent
)
106 public void onDestroy()
111 * Register a listener with this Service. We assume this is called from
112 * the main thread so no synchronization is happening.
114 * @param listener listener to register
116 public void registerListener(VpnStateListener listener
)
118 mListeners
.add(listener
);
122 * Unregister a listener from this Service.
124 * @param listener listener to unregister
126 public void unregisterListener(VpnStateListener listener
)
128 mListeners
.remove(listener
);
132 * Get the current VPN profile.
136 public VpnProfile
getProfile()
137 { /* only updated from the main thread so no synchronization needed */
142 * Get the current connection ID. May be used to track which state
143 * changes have already been handled.
145 * Is increased when startConnection() is called.
147 * @return connection ID
149 public long getConnectionID()
150 { /* only updated from the main thread so no synchronization needed */
151 return mConnectionID
;
155 * Get the current state.
159 public State
getState()
160 { /* only updated from the main thread so no synchronization needed */
165 * Get the current error, if any.
169 public ErrorState
getErrorState()
170 { /* only updated from the main thread so no synchronization needed */
175 * Get a description of the current error, if any.
177 * @return error description text id
179 public int getErrorText()
184 if (mImcState
== ImcState
.BLOCK
)
186 return R
.string
.error_assessment_failed
;
190 return R
.string
.error_auth_failed
;
192 case PEER_AUTH_FAILED
:
193 return R
.string
.error_peer_auth_failed
;
195 return R
.string
.error_lookup_failed
;
197 return R
.string
.error_unreachable
;
198 case PASSWORD_MISSING
:
199 return R
.string
.error_password_missing
;
200 case CERTIFICATE_UNAVAILABLE
:
201 return R
.string
.error_certificate_unavailable
;
203 return R
.string
.error_generic
;
208 * Get the current IMC state, if any.
212 public ImcState
getImcState()
213 { /* only updated from the main thread so no synchronization needed */
218 * Get the remediation instructions, if any.
220 * @return read-only list of instructions
222 public List
<RemediationInstruction
> getRemediationInstructions()
223 { /* only updated from the main thread so no synchronization needed */
224 return Collections
.unmodifiableList(mRemediationInstructions
);
228 * Disconnect any existing connection and shutdown the daemon, the
229 * VpnService is not stopped but it is reset so new connections can be
232 public void disconnect()
234 /* as soon as the TUN device is created by calling establish() on the
235 * VpnService.Builder object the system binds to the service and keeps
236 * bound until the file descriptor of the TUN device is closed. thus
237 * calling stopService() here would not stop (destroy) the service yet,
238 * instead we call startService() with a specific action which shuts down
239 * the daemon (and closes the TUN device, if any) */
240 Context context
= getApplicationContext();
241 Intent intent
= new Intent(context
, CharonVpnService
.class);
242 intent
.setAction(CharonVpnService
.DISCONNECT_ACTION
);
243 context
.startService(intent
);
247 * Reconnect to the previous profile.
249 public void reconnect()
251 if (mProfile
== null)
255 Bundle profileInfo
= new Bundle();
256 profileInfo
.putLong(VpnProfileDataSource
.KEY_ID
, mProfile
.getId());
257 /* pass the previous password along */
258 profileInfo
.putString(VpnProfileDataSource
.KEY_PASSWORD
, mProfile
.getPassword());
259 /* we assume we have the necessary permission */
260 Context context
= getApplicationContext();
261 Intent intent
= new Intent(context
, CharonVpnService
.class);
262 intent
.putExtras(profileInfo
);
263 context
.startService(intent
);
267 * Update state and notify all listeners about the change. By using a Handler
268 * this is done from the main UI thread and not the initial reporter thread.
269 * Also, in doing the actual state change from the main thread, listeners
270 * see all changes and none are skipped.
272 * @param change the state update to perform before notifying listeners, returns true if state changed
274 private void notifyListeners(final Callable
<Boolean
> change
)
276 mHandler
.post(new Runnable() {
283 { /* otherwise there is no need to notify the listeners */
284 for (VpnStateListener listener
: mListeners
)
286 listener
.stateChanged();
299 * Called when a connection is started. Sets the currently active VPN
300 * profile, resets IMC and Error state variables, sets the State to
301 * CONNECTING, increases the connection ID, and notifies all listeners.
303 * May be called from threads other than the main thread.
305 * @param profile current profile
307 public void startConnection(final VpnProfile profile
)
309 notifyListeners(new Callable
<Boolean
>() {
311 public Boolean
call() throws Exception
313 VpnStateService
.this.mConnectionID
++;
314 VpnStateService
.this.mProfile
= profile
;
315 VpnStateService
.this.mState
= State
.CONNECTING
;
316 VpnStateService
.this.mError
= ErrorState
.NO_ERROR
;
317 VpnStateService
.this.mImcState
= ImcState
.UNKNOWN
;
318 VpnStateService
.this.mRemediationInstructions
.clear();
325 * Update the state and notify all listeners, if changed.
327 * May be called from threads other than the main thread.
329 * @param state new state
331 public void setState(final State state
)
333 notifyListeners(new Callable
<Boolean
>() {
335 public Boolean
call() throws Exception
337 if (VpnStateService
.this.mState
!= state
)
339 VpnStateService
.this.mState
= state
;
348 * Set the current error state and notify all listeners, if changed.
350 * May be called from threads other than the main thread.
352 * @param error error state
354 public void setError(final ErrorState error
)
356 notifyListeners(new Callable
<Boolean
>() {
358 public Boolean
call() throws Exception
360 if (VpnStateService
.this.mError
!= error
)
362 VpnStateService
.this.mError
= error
;
371 * Set the current IMC state and notify all listeners, if changed.
373 * Setting the state to UNKNOWN clears all remediation instructions.
375 * May be called from threads other than the main thread.
377 * @param state IMC state
379 public void setImcState(final ImcState state
)
381 notifyListeners(new Callable
<Boolean
>() {
383 public Boolean
call() throws Exception
385 if (state
== ImcState
.UNKNOWN
)
387 VpnStateService
.this.mRemediationInstructions
.clear();
389 if (VpnStateService
.this.mImcState
!= state
)
391 VpnStateService
.this.mImcState
= state
;
400 * Add the given remediation instruction to the internal list. Listeners
403 * Instructions are cleared if the IMC state is set to UNKNOWN.
405 * May be called from threads other than the main thread.
407 * @param instruction remediation instruction
409 public void addRemediationInstruction(final RemediationInstruction instruction
)
411 mHandler
.post(new Runnable() {
415 VpnStateService
.this.mRemediationInstructions
.add(instruction
);