android: VpnStateService - single retry countdown, isolated listeners
- remove pending RETRY_MSG on reset/set: a Retry tap during a backoff started a second countdown chain (double-speed ticks, two reconnects) - notify listeners over a copy with per-listener error handling - disconnect() tolerates the VPN service not running Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
358dcb94b2
commit
f2e4ffe860
1 file changed
+39
-14
+39
-14
@@ -41,6 +41,7 @@ import org.strongswan.android.ui.VpnProfileControlActivity;
|
|||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -199,6 +200,27 @@ public class VpnStateService extends Service
|
|||||||
mListeners.remove(listener);
|
mListeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify all registered listeners about a state change. Iterates over a
|
||||||
|
* copy (a listener may unregister itself from its callback) and isolates
|
||||||
|
* the listeners from each other: one failing listener must neither skip
|
||||||
|
* the others nor crash the main thread.
|
||||||
|
*/
|
||||||
|
private void notifyStateListeners()
|
||||||
|
{
|
||||||
|
for (VpnStateListener listener : new ArrayList<>(mListeners))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
listener.stateChanged();
|
||||||
|
}
|
||||||
|
catch (RuntimeException e)
|
||||||
|
{
|
||||||
|
DiagLog.i(TAG, "state listener failed: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current VPN profile.
|
* Get the current VPN profile.
|
||||||
*
|
*
|
||||||
@@ -373,7 +395,15 @@ public class VpnStateService extends Service
|
|||||||
Context context = getApplicationContext();
|
Context context = getApplicationContext();
|
||||||
Intent intent = new Intent(context, CharonVpnService.class);
|
Intent intent = new Intent(context, CharonVpnService.class);
|
||||||
intent.setAction(CharonVpnService.DISCONNECT_ACTION);
|
intent.setAction(CharonVpnService.DISCONNECT_ACTION);
|
||||||
context.startService(intent);
|
try
|
||||||
|
{
|
||||||
|
context.startService(intent);
|
||||||
|
}
|
||||||
|
catch (IllegalStateException e)
|
||||||
|
{ /* background start of a service that is not running (Android 8+):
|
||||||
|
* without the service there is no tunnel to tear down */
|
||||||
|
DiagLog.i(TAG, "disconnect: VPN service not running (" + e.getMessage() + ")");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -589,10 +619,7 @@ public class VpnStateService extends Service
|
|||||||
{
|
{
|
||||||
if (change.call())
|
if (change.call())
|
||||||
{ /* otherwise there is no need to notify the listeners */
|
{ /* otherwise there is no need to notify the listeners */
|
||||||
for (VpnStateListener listener : mListeners)
|
notifyStateListeners();
|
||||||
{
|
|
||||||
listener.stateChanged();
|
|
||||||
}
|
|
||||||
/* the home screen widget mirrors the state */
|
/* the home screen widget mirrors the state */
|
||||||
VpnProfile widgetProfile = mProfile;
|
VpnProfile widgetProfile = mProfile;
|
||||||
ShxWidgetState.publish(VpnStateService.this, mState, getErrorState(),
|
ShxWidgetState.publish(VpnStateService.this, mState, getErrorState(),
|
||||||
@@ -602,7 +629,7 @@ public class VpnStateService extends Service
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
e.printStackTrace();
|
DiagLog.i(TAG, "state change failed: " + e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -766,6 +793,9 @@ public class VpnStateService extends Service
|
|||||||
mRetryTimeout = mRetryIn = mTimeoutProvider.getTimeout(error);
|
mRetryTimeout = mRetryIn = mTimeoutProvider.getTimeout(error);
|
||||||
mRetryFrozen = false;
|
mRetryFrozen = false;
|
||||||
DiagLog.i(TAG, "error " + error + ", retry in " + (mRetryTimeout / 1000) + "s");
|
DiagLog.i(TAG, "error " + error + ", retry in " + (mRetryTimeout / 1000) + "s");
|
||||||
|
/* never run two countdown chains in parallel (double-speed countdown,
|
||||||
|
* double notification updates and two reconnects at zero) */
|
||||||
|
mHandler.removeMessages(RETRY_MSG);
|
||||||
if (mRetryTimeout <= 0)
|
if (mRetryTimeout <= 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -778,6 +808,7 @@ public class VpnStateService extends Service
|
|||||||
*/
|
*/
|
||||||
private void resetRetryTimer()
|
private void resetRetryTimer()
|
||||||
{
|
{
|
||||||
|
mHandler.removeMessages(RETRY_MSG);
|
||||||
mRetryTimeout = 0;
|
mRetryTimeout = 0;
|
||||||
mRetryIn = 0;
|
mRetryIn = 0;
|
||||||
mRetryFrozen = false;
|
mRetryFrozen = false;
|
||||||
@@ -858,10 +889,7 @@ public class VpnStateService extends Service
|
|||||||
/* calculate next interval before notifying listeners */
|
/* calculate next interval before notifying listeners */
|
||||||
long next = SystemClock.uptimeMillis() + retryStep(service.mRetryIn);
|
long next = SystemClock.uptimeMillis() + retryStep(service.mRetryIn);
|
||||||
|
|
||||||
for (VpnStateListener listener : service.mListeners)
|
service.notifyStateListeners();
|
||||||
{
|
|
||||||
listener.stateChanged();
|
|
||||||
}
|
|
||||||
sendMessageAtTime(obtainMessage(RETRY_MSG), next);
|
sendMessageAtTime(obtainMessage(RETRY_MSG), next);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -872,10 +900,7 @@ public class VpnStateService extends Service
|
|||||||
* ticking every second */
|
* ticking every second */
|
||||||
service.mRetryFrozen = true;
|
service.mRetryFrozen = true;
|
||||||
service.mRetryIn = RETRY_SUPPRESS_MS;
|
service.mRetryIn = RETRY_SUPPRESS_MS;
|
||||||
for (VpnStateListener listener : service.mListeners)
|
service.notifyStateListeners();
|
||||||
{
|
|
||||||
listener.stateChanged();
|
|
||||||
}
|
|
||||||
sendMessageAtTime(obtainMessage(RETRY_MSG), SystemClock.uptimeMillis() + RETRY_SUPPRESS_MS);
|
sendMessageAtTime(obtainMessage(RETRY_MSG), SystemClock.uptimeMillis() + RETRY_SUPPRESS_MS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user