import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Message;
+import android.os.SystemClock;
import org.strongswan.android.R;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.logic.imc.ImcState;
import org.strongswan.android.logic.imc.RemediationInstruction;
+import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
private ErrorState mError = ErrorState.NO_ERROR;
private ImcState mImcState = ImcState.UNKNOWN;
private final LinkedList<RemediationInstruction> mRemediationInstructions = new LinkedList<RemediationInstruction>();
+ private static long RETRY_INTERVAL = 1000;
+ private static int RETRY_MSG = 1;
+ private long mRetryTimeout;
+ private long mRetryIn;
public enum State
{
{
/* 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();
+ mHandler = new RetryHandler(this);
}
@Override
return mConnectionID;
}
+ /**
+ * Get the total number of seconds until there is an automatic retry to reconnect.
+ * @return total number of seconds until the retry
+ */
+ public int getRetryTimeout()
+ {
+ return (int)(mRetryTimeout / 1000);
+ }
+
+ /**
+ * Get the number of seconds until there is an automatic retry to reconnect.
+ * @return number of seconds until the retry
+ */
+ public int getRetryIn()
+ {
+ return (int)(mRetryIn / 1000);
+ }
+
/**
* Get the current state.
*
*/
public void disconnect()
{
+ resetRetryTimer();
/* 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
@Override
public Boolean call() throws Exception
{
+ resetRetryTimer();
VpnStateService.this.mConnectionID++;
VpnStateService.this.mProfile = profile;
VpnStateService.this.mState = State.CONNECTING;
{
if (VpnStateService.this.mError != error)
{
+ if (VpnStateService.this.mError == ErrorState.NO_ERROR)
+ {
+ setRetryTimer(error);
+ }
+ else if (error == ErrorState.NO_ERROR)
+ {
+ resetRetryTimer();
+ }
VpnStateService.this.mError = error;
return true;
}
}
});
}
+
+ /**
+ * Sets the retry timer
+ */
+ private void setRetryTimer(ErrorState error)
+ {
+ long timeout;
+
+ switch (error)
+ {
+ case AUTH_FAILED:
+ timeout = 20000;
+ break;
+ case PEER_AUTH_FAILED:
+ timeout = 20000;
+ break;
+ case LOOKUP_FAILED:
+ timeout = 10000;
+ break;
+ case UNREACHABLE:
+ timeout = 10000;
+ break;
+ case PASSWORD_MISSING:
+ /* this needs user intervention (entering the password) */
+ timeout = 0;
+ break;
+ case CERTIFICATE_UNAVAILABLE:
+ /* if this is because the device has to be unlocked we might be able to reconnect */
+ timeout = 10000;
+ break;
+ default:
+ timeout = 20000;
+ break;
+ }
+ mRetryTimeout = mRetryIn = timeout;
+ if (timeout <= 0)
+ {
+ return;
+ }
+ mHandler.sendMessageAtTime(mHandler.obtainMessage(RETRY_MSG), SystemClock.uptimeMillis() + RETRY_INTERVAL);
+ }
+
+ /**
+ * Reset the retry timer
+ */
+ private void resetRetryTimer()
+ {
+ mRetryTimeout = 0;
+ mRetryIn = 0;
+ }
+
+ /**
+ * Special Handler subclass that handles the retry countdown (more accurate than CountDownTimer)
+ */
+ private static class RetryHandler extends Handler {
+ WeakReference<VpnStateService> mService;
+
+ public RetryHandler(VpnStateService service)
+ {
+ mService = new WeakReference<>(service);
+ }
+
+ @Override
+ public void handleMessage(Message msg)
+ {
+ /* handle retry countdown */
+ if (mService.get().mRetryTimeout <= 0)
+ {
+ return;
+ }
+ mService.get().mRetryIn -= RETRY_INTERVAL;
+ if (mService.get().mRetryIn > 0)
+ {
+ /* calculate next interval before notifying listeners */
+ long next = SystemClock.uptimeMillis() + RETRY_INTERVAL;
+
+ for (VpnStateListener listener : mService.get().mListeners)
+ {
+ listener.stateChanged();
+ }
+ sendMessageAtTime(obtainMessage(RETRY_MSG), next);
+ }
+ else
+ {
+ mService.get().reconnect();
+ }
+ }
+ }
}
import org.strongswan.android.logic.VpnStateService.ErrorState;
import org.strongswan.android.logic.VpnStateService.State;
import org.strongswan.android.logic.VpnStateService.VpnStateListener;
-import org.strongswan.android.logic.imc.ImcState;
-import org.strongswan.android.logic.imc.RemediationInstruction;
-
-import java.util.ArrayList;
-import java.util.List;
public class VpnStateFragment extends Fragment implements VpnStateListener
{
private LinearLayout mErrorView;
private TextView mErrorText;
private Button mErrorRetry;
- private Button mDismissError;
+ private Button mShowLog;
private long mErrorConnectionID;
private VpnStateService mService;
private final ServiceConnection mServiceConnection = new ServiceConnection()
View view = inflater.inflate(R.layout.vpn_state_fragment, null);
mActionButton = (Button)view.findViewById(R.id.action);
+ mActionButton.setOnClickListener(v -> clearError());
enableActionButton(null);
mErrorView = view.findViewById(R.id.vpn_error);
mErrorText = view.findViewById(R.id.vpn_error_text);
mErrorRetry = view.findViewById(R.id.retry);
- mDismissError = view.findViewById(R.id.dismiss_error);
+ mShowLog = view.findViewById(R.id.show_log);
mProgress = (ProgressBar)view.findViewById(R.id.progress);
mStateView = (TextView)view.findViewById(R.id.vpn_state);
mColorStateBase = mStateView.getCurrentTextColor();
mService.reconnect();
}
});
- mDismissError.setOnClickListener(v -> clearError());
+ mShowLog.setOnClickListener(v -> {
+ Intent intent = new Intent(getActivity(), LogActivity.class);
+ startActivity(intent);
+ });
return view;
}
VpnProfile profile = mService.getProfile();
State state = mService.getState();
ErrorState error = mService.getErrorState();
- ImcState imcState = mService.getImcState();
String name = "";
if (getActivity() == null)
name = profile.getName();
}
- if (reportError(connectionID, name, error, imcState))
+ if (reportError(connectionID, name, error))
{
return;
}
mProfileNameView.setText(name);
+ mProgress.setIndeterminate(true);
switch (state)
{
}
}
- private boolean reportError(long connectionID, String name, ErrorState error, ImcState imcState)
+ private boolean reportError(long connectionID, String name, ErrorState error)
{
if (error == ErrorState.NO_ERROR)
{
mErrorConnectionID = connectionID;
mProfileNameView.setText(name);
showProfile(true);
- mProgress.setVisibility(View.GONE);
mStateView.setText(R.string.state_error);
mStateView.setTextColor(mColorStateError);
- enableActionButton(getString(R.string.show_log));
- mActionButton.setOnClickListener(v -> {
- Intent intent = new Intent(getActivity(), LogActivity.class);
- startActivity(intent);
- });
- mErrorText.setText(getString(R.string.error_format, getString(mService.getErrorText())));
+ enableActionButton(getString(android.R.string.cancel));
+
+ int retry = mService.getRetryIn();
+ if (retry > 0)
+ {
+ mProgress.setIndeterminate(false);
+ mProgress.setMax(mService.getRetryTimeout());
+ mProgress.setProgress(retry);
+ mProgress.setVisibility(View.VISIBLE);
+ mStateView.setText(getResources().getQuantityString(R.plurals.retry_in, retry, retry));
+ }
+ else if (mService.getRetryTimeout() <= 0)
+ {
+ mProgress.setVisibility(View.GONE);
+ }
+
+ String text = getString(R.string.error_format, getString(mService.getErrorText()));
+ mErrorText.setText(text);
mErrorView.setVisibility(View.VISIBLE);
return true;
}
mActionButton.setText(text);
mActionButton.setEnabled(text != null);
mActionButton.setVisibility(text != null ? View.VISIBLE : View.GONE);
- mActionButton.setOnClickListener(mDisconnectListener);
}
private void clearError()
{
if (mService != null)
{
+ mService.disconnect();
if (mService.getConnectionID() == mErrorConnectionID)
{
- mService.disconnect();
mService.setError(ErrorState.NO_ERROR);
}
}
- updateView();
}
}
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
+ android:layout_marginRight="20dp"
android:layout_marginTop="24dp"
android:layout_marginBottom="12dp"
android:text="Failed to establish VPN: Server is unreachable"
android:gravity="end" >
<Button
- android:id="@+id/dismiss_error"
+ android:id="@+id/show_log"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
- android:text="@string/dismiss"
+ android:text="@string/show_log"
android:textColor="@color/primary"
android:textSize="14sp"
android:textStyle="bold"
<string name="disconnect_active_connection">Dies trennt die aktuelle VPN Verbindung!</string>
<string name="connect">Verbinden</string>
<string name="retry">Wiederholen</string>
+ <plurals name="retry_in">
+ <item quantity="one">Wiederholen in %1$d Sekunde</item>
+ <item quantity="other">Wiederholen in %1$d Sekunden</item>
+ </plurals>
+ <string name="cancel_retry">Wiederholen abbrechen</string>
<!-- Quick Settings tile -->
<string name="tile_default">VPN umschalten</string>
<string name="disconnect_active_connection">This will disconnect the active VPN connection!</string>
<string name="connect">Połącz</string>
<string name="retry">Retry</string>
+ <plurals name="retry_in">
+ <item quantity="one">Retry in %1$d second</item>
+ <item quantity="other">Retry in %1$d seconds</item>
+ </plurals>
+ <string name="cancel_retry">Cancel retry</string>
<!-- Quick Settings tile -->
<string name="tile_default">Toggle VPN</string>
<string name="disconnect_active_connection">This will disconnect the active VPN connection!</string>
<string name="connect">Соединить</string>
<string name="retry">Retry</string>
+ <plurals name="retry_in">
+ <item quantity="one">Retry in %1$d second</item>
+ <item quantity="other">Retry in %1$d seconds</item>
+ </plurals>
+ <string name="cancel_retry">Cancel retry</string>
<!-- Quick Settings tile -->
<string name="tile_default">Toggle VPN</string>
<string name="disconnect_active_connection">This will disconnect the active VPN connection!</string>
<string name="connect">Підключити</string>
<string name="retry">Retry</string>
+ <plurals name="retry_in">
+ <item quantity="one">Retry in %1$d second</item>
+ <item quantity="other">Retry in %1$d seconds</item>
+ </plurals>
+ <string name="cancel_retry">Cancel retry</string>
<!-- Quick Settings tile -->
<string name="tile_default">Toggle VPN</string>
<string name="disconnect_active_connection">This will disconnect the active VPN connection!</string>
<string name="connect">连接</string>
<string name="retry">Retry</string>
+ <plurals name="retry_in">
+ <item quantity="one">Retry in %1$d second</item>
+ <item quantity="other">Retry in %1$d seconds</item>
+ </plurals>
+ <string name="cancel_retry">Cancel retry</string>
<!-- Quick Settings tile -->
<string name="tile_default">Toggle VPN</string>
<string name="disconnect_active_connection">This will disconnect the active VPN connection!</string>
<string name="connect">連線</string>
<string name="retry">Retry</string>
+ <plurals name="retry_in">
+ <item quantity="one">Retry in %1$d second</item>
+ <item quantity="other">Retry in %1$d seconds</item>
+ </plurals>
+ <string name="cancel_retry">Cancel retry</string>
<!-- Quick Settings tile -->
<string name="tile_default">Toggle VPN</string>
<string name="disconnect_active_connection">This will disconnect the active VPN connection!</string>
<string name="connect">Connect</string>
<string name="retry">Retry</string>
+ <plurals name="retry_in">
+ <item quantity="one">Retry in %1$d second</item>
+ <item quantity="other">Retry in %1$d seconds</item>
+ </plurals>
+ <string name="cancel_retry">Cancel retry</string>
<!-- Quick Settings tile -->
<string name="tile_default">Toggle VPN</string>