diff --git a/android/src/main/java/com/tailscale/ipn/IPNReceiver.java b/android/src/main/java/com/tailscale/ipn/IPNReceiver.java index ea0ed92701..87ab33c023 100644 --- a/android/src/main/java/com/tailscale/ipn/IPNReceiver.java +++ b/android/src/main/java/com/tailscale/ipn/IPNReceiver.java @@ -6,9 +6,11 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import androidx.work.Data; +import androidx.work.Data; +import androidx.work.ExistingWorkPolicy; import androidx.work.OneTimeWorkRequest; +import androidx.work.OutOfQuotaPolicy; import androidx.work.WorkManager; import java.util.Objects; @@ -20,26 +22,56 @@ public class IPNReceiver extends BroadcastReceiver { public static final String INTENT_CONNECT_VPN = "com.tailscale.ipn.CONNECT_VPN"; public static final String INTENT_DISCONNECT_VPN = "com.tailscale.ipn.DISCONNECT_VPN"; - private static final String INTENT_USE_EXIT_NODE = "com.tailscale.ipn.USE_EXIT_NODE"; + // Unique work names prevent connect/disconnect flapping from enqueuing a long backlog. + private static final String WORK_CONNECT = "ipn-connect-vpn"; + private static final String WORK_DISCONNECT = "ipn-disconnect-vpn"; + private static final String WORK_USE_EXIT_NODE = "ipn-use-exit-node"; + @Override public void onReceive(Context context, Intent intent) { - WorkManager workManager = WorkManager.getInstance(context); + if (intent == null) return; - // On the relevant action, start the relevant worker, which can stay active for longer than this receiver can. - if (Objects.equals(intent.getAction(), INTENT_CONNECT_VPN)) { - workManager.enqueue(new OneTimeWorkRequest.Builder(StartVPNWorker.class).build()); - } else if (Objects.equals(intent.getAction(), INTENT_DISCONNECT_VPN)) { - workManager.enqueue(new OneTimeWorkRequest.Builder(StopVPNWorker.class).build()); - } - else if (Objects.equals(intent.getAction(), INTENT_USE_EXIT_NODE)) { + final WorkManager workManager = WorkManager.getInstance(context); + final String action = intent.getAction(); + + if (Objects.equals(action, INTENT_CONNECT_VPN)) { + OneTimeWorkRequest req = + new OneTimeWorkRequest.Builder(StartVPNWorker.class) + .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) + .addTag(WORK_CONNECT) + .build(); + + workManager.enqueueUniqueWork(WORK_CONNECT, ExistingWorkPolicy.REPLACE, req); + + } else if (Objects.equals(action, INTENT_DISCONNECT_VPN)) { + OneTimeWorkRequest req = + new OneTimeWorkRequest.Builder(StopVPNWorker.class) + .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) + .addTag(WORK_DISCONNECT) + .build(); + + workManager.enqueueUniqueWork(WORK_DISCONNECT, ExistingWorkPolicy.REPLACE, req); + + } else if (Objects.equals(action, INTENT_USE_EXIT_NODE)) { String exitNode = intent.getStringExtra("exitNode"); boolean allowLanAccess = intent.getBooleanExtra("allowLanAccess", false); - Data.Builder workData = new Data.Builder(); - workData.putString(UseExitNodeWorker.EXIT_NODE_NAME, exitNode); - workData.putBoolean(UseExitNodeWorker.ALLOW_LAN_ACCESS, allowLanAccess); - workManager.enqueue(new OneTimeWorkRequest.Builder(UseExitNodeWorker.class).setInputData(workData.build()).build()); + + Data input = + new Data.Builder() + .putString(UseExitNodeWorker.EXIT_NODE_NAME, exitNode) + .putBoolean(UseExitNodeWorker.ALLOW_LAN_ACCESS, allowLanAccess) + .build(); + + OneTimeWorkRequest req = + new OneTimeWorkRequest.Builder(UseExitNodeWorker.class) + .setInputData(input) + .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) + .addTag(WORK_USE_EXIT_NODE) + .build(); + + workManager.enqueueUniqueWork(WORK_USE_EXIT_NODE, ExistingWorkPolicy.REPLACE, req); } } } diff --git a/android/src/main/java/com/tailscale/ipn/IPNService.kt b/android/src/main/java/com/tailscale/ipn/IPNService.kt index 20f3c09c25..ced3e18a47 100644 --- a/android/src/main/java/com/tailscale/ipn/IPNService.kt +++ b/android/src/main/java/com/tailscale/ipn/IPNService.kt @@ -54,7 +54,7 @@ open class IPNService : VpnService(), libtailscale.IPNService { START_NOT_STICKY } ACTION_START_VPN -> { - scope.launch { showForegroundNotification() } + showForegroundNotification() app.setWantRunning(true) Libtailscale.requestVPN(this) START_STICKY @@ -78,7 +78,7 @@ open class IPNService : VpnService(), libtailscale.IPNService { // This means that we were restarted after the service was killed // (potentially due to OOM). if (UninitializedApp.get().isAbleToStartVPN()) { - scope.launch { showForegroundNotification() } + showForegroundNotification() App.get() Libtailscale.requestVPN(this) START_STICKY