/* * Copyright (C) 2012-2013 Tobias Brunner * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ package org.strongswan.android.logic; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Callable; import org.strongswan.android.data.VpnProfile; import org.strongswan.android.logic.imc.ImcState; import org.strongswan.android.logic.imc.RemediationInstruction; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.Handler; import android.os.IBinder; //CMA import com.swisscom.safeconnect.utils.Config; public class VpnStateService extends Service { private final List<VpnStateListener> mListeners = new ArrayList<VpnStateListener>(); private final IBinder mBinder = new LocalBinder(); private long mConnectionID = 0; private Handler mHandler; private VpnProfile mProfile; private State mState = State.DISABLED; private ErrorState mError = ErrorState.NO_ERROR; private ImcState mImcState = ImcState.UNKNOWN; private final LinkedList<RemediationInstruction> mRemediationInstructions = new LinkedList<RemediationInstruction>(); public enum State { DISABLED, CONNECTING, CONNECTED, DISCONNECTING, } public enum ErrorState { NO_ERROR, AUTH_FAILED, PEER_AUTH_FAILED, LOOKUP_FAILED, UNREACHABLE, GENERIC_ERROR, } /** * Listener interface for bound clients that are interested in changes to * this Service. */ public interface VpnStateListener { public void stateChanged(); } /** * Simple Binder that allows to directly access this Service class itself * after binding to it. */ public class LocalBinder extends Binder { public VpnStateService getService() { return VpnStateService.this; } } @Override public void onCreate() { /* this handler allows us to notify listeners from the UI thread and * not from the threads that actually report any state changes */ mHandler = new Handler(); } @Override public IBinder onBind(Intent intent) { return mBinder; } @Override public void onDestroy() { } /** * Register a listener with this Service. We assume this is called from * the main thread so no synchronization is happening. * * @param listener listener to register */ public void registerListener(VpnStateListener listener) { mListeners.add(listener); } /** * Unregister a listener from this Service. * * @param listener listener to unregister */ public void unregisterListener(VpnStateListener listener) { mListeners.remove(listener); } /** * Get the current VPN profile. * * @return profile */ public VpnProfile getProfile() { /* only updated from the main thread so no synchronization needed */ return mProfile; } /** * Get the current connection ID. May be used to track which state * changes have already been handled. * * Is increased when startConnection() is called. * * @return connection ID */ public long getConnectionID() { /* only updated from the main thread so no synchronization needed */ return mConnectionID; } /** * Get the current state. * * @return state */ public State getState() { /* only updated from the main thread so no synchronization needed */ return mState; } /** * Get the current error, if any. * * @return error */ public ErrorState getErrorState() { /* only updated from the main thread so no synchronization needed */ return mError; } /** * Get the current IMC state, if any. * * @return imc state */ public ImcState getImcState() { /* only updated from the main thread so no synchronization needed */ return mImcState; } /** * Get the remediation instructions, if any. * * @return read-only list of instructions */ public List<RemediationInstruction> getRemediationInstructions() { /* only updated from the main thread so no synchronization needed */ return Collections.unmodifiableList(mRemediationInstructions); } /** * Disconnect any existing connection and shutdown the daemon, the * VpnService is not stopped but it is reset so new connections can be * started. */ public void disconnect() { /* as soon as the TUN device is created by calling establish() on the * VpnService.Builder object the system binds to the service and keeps * bound until the file descriptor of the TUN device is closed. thus * calling stopService() here would not stop (destroy) the service yet, * instead we call startService() with an empty Intent which shuts down * the daemon (and closes the TUN device, if any) */ Context context = getApplicationContext(); Intent intent = new Intent(context, CharonVpnService.class); context.startService(intent); } /** * Update state and notify all listeners about the change. By using a Handler * this is done from the main UI thread and not the initial reporter thread. * Also, in doing the actual state change from the main thread, listeners * see all changes and none are skipped. * * @param change the state update to perform before notifying listeners, returns true if state changed */ private void notifyListeners(final Callable<Boolean> change) { mHandler.post(new Runnable() { @Override public void run() { try { if (change.call()) { /* otherwise there is no need to notify the listeners */ for (VpnStateListener listener : mListeners) { listener.stateChanged(); } //CMA //Inform widget from service, as all the activities might be stopped Config.getInstance().setVpnState(getState()); if (getState().equals(State.CONNECTED) || getState().equals(State.DISABLED)) { sendBroadcast(new Intent(Config.VPN_CONNECT)); } } } catch (Exception e) { e.printStackTrace(); } } }); } /** * Called when a connection is started. Sets the currently active VPN * profile, resets IMC and Error state variables, sets the State to * CONNECTING, increases the connection ID, and notifies all listeners. * * May be called from threads other than the main thread. * * @param profile current profile */ public void startConnection(final VpnProfile profile) { notifyListeners(new Callable<Boolean>() { @Override public Boolean call() throws Exception { VpnStateService.this.mConnectionID++; VpnStateService.this.mProfile = profile; VpnStateService.this.mState = State.CONNECTING; VpnStateService.this.mError = ErrorState.NO_ERROR; VpnStateService.this.mImcState = ImcState.UNKNOWN; VpnStateService.this.mRemediationInstructions.clear(); return true; } }); } /** * Update the state and notify all listeners, if changed. * * May be called from threads other than the main thread. * * @param state new state */ public void setState(final State state) { notifyListeners(new Callable<Boolean>() { @Override public Boolean call() throws Exception { if (VpnStateService.this.mState != state) { VpnStateService.this.mState = state; return true; } return false; } }); } /** * Set the current error state and notify all listeners, if changed. * * May be called from threads other than the main thread. * * @param error error state */ public void setError(final ErrorState error) { notifyListeners(new Callable<Boolean>() { @Override public Boolean call() throws Exception { if (VpnStateService.this.mError != error) { VpnStateService.this.mError = error; return true; } return false; } }); } /** * Set the current IMC state and notify all listeners, if changed. * * Setting the state to UNKNOWN clears all remediation instructions. * * May be called from threads other than the main thread. * * @param error error state */ public void setImcState(final ImcState state) { notifyListeners(new Callable<Boolean>() { @Override public Boolean call() throws Exception { if (state == ImcState.UNKNOWN) { VpnStateService.this.mRemediationInstructions.clear(); } if (VpnStateService.this.mImcState != state) { VpnStateService.this.mImcState = state; return true; } return false; } }); } /** * Add the given remediation instruction to the internal list. Listeners * are not notified. * * Instructions are cleared if the IMC state is set to UNKNOWN. * * May be called from threads other than the main thread. * * @param instruction remediation instruction */ public void addRemediationInstruction(final RemediationInstruction instruction) { mHandler.post(new Runnable() { @Override public void run() { VpnStateService.this.mRemediationInstructions.add(instruction); } }); } }