Add intents (#87)
IPNReceiver: Add intents to connect and disconnect VPN Added a new class IPNReceiver to listen to intents silently and connect and disconnect the VPN. This uses workers to avoid doing too much in the IPNReceiver which is to be avoided according to documentation. Also includes a fix for vpn occasionally not starting. Think this was due to a race condition, but now only sets autoConnect to false when we know a connection is connecting or connected. Fixes https://github.com/tailscale/tailscale/issues/3547 Updates https://github.com/tailscale/tailscale/issues/2481 Signed-off-by: Brett Jenkins <brett@brettjenkins.co.uk>bradfitz/shared_user_dev
parent
24ba39121f
commit
eb9599540c
@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2023 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package com.tailscale.ipn;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import androidx.work.WorkManager;
|
||||
import androidx.work.OneTimeWorkRequest;
|
||||
|
||||
public class IPNReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
WorkManager workManager = WorkManager.getInstance(context);
|
||||
|
||||
// On the relevant action, start the relevant worker, which can stay active for longer than this receiver can.
|
||||
if (intent.getAction() == "com.tailscale.ipn.CONNECT_VPN") {
|
||||
workManager.enqueue(new OneTimeWorkRequest.Builder(StartVPNWorker.class).build());
|
||||
} else if (intent.getAction() == "com.tailscale.ipn.DISCONNECT_VPN") {
|
||||
workManager.enqueue(new OneTimeWorkRequest.Builder(StopVPNWorker.class).build());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2023 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package com.tailscale.ipn;
|
||||
|
||||
import androidx.work.Worker;
|
||||
import android.content.Context;
|
||||
import androidx.work.WorkerParameters;
|
||||
import android.net.VpnService;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
public final class StartVPNWorker extends Worker {
|
||||
|
||||
public StartVPNWorker(
|
||||
Context appContext,
|
||||
WorkerParameters workerParams) {
|
||||
super(appContext, workerParams);
|
||||
}
|
||||
|
||||
@Override public Result doWork() {
|
||||
// We will start the VPN from the background
|
||||
App app = ((App)getApplicationContext());
|
||||
app.autoConnect = true;
|
||||
// We need to make sure we prepare the VPN Service, just in case it isn't prepared.
|
||||
|
||||
Intent intent = VpnService.prepare(app);
|
||||
if (intent == null) {
|
||||
// If null then the VPN is already prepared and/or it's just been prepared because we have permission
|
||||
app.startVPN();
|
||||
return Result.success();
|
||||
} else {
|
||||
// This VPN possibly doesn't have permission, we need to display a notification which when clicked launches the intent provided.
|
||||
android.util.Log.e("StartVPNWorker", "Tailscale doesn't have permission from the system to start VPN. Launching the intent provided.");
|
||||
|
||||
// Send notification
|
||||
NotificationManager notificationManager = (NotificationManager) app.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
String channelId = "start_vpn_channel";
|
||||
|
||||
// Use createNotificationChannel method from App.java
|
||||
app.createNotificationChannel(channelId, "Start VPN Channel", NotificationManager.IMPORTANCE_DEFAULT);
|
||||
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
int pendingIntentFlags = PendingIntent.FLAG_ONE_SHOT | (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? PendingIntent.FLAG_IMMUTABLE : 0);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(app, 0, intent, pendingIntentFlags);
|
||||
|
||||
Notification notification = new Notification.Builder(app, channelId)
|
||||
.setContentTitle("Tailscale Connection Failed")
|
||||
.setContentText("Tap here to renew permission.")
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
.build();
|
||||
|
||||
notificationManager.notify(1, notification);
|
||||
|
||||
return Result.failure();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2023 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package com.tailscale.ipn;
|
||||
|
||||
import androidx.work.Worker;
|
||||
import android.content.Context;
|
||||
import androidx.work.WorkerParameters;
|
||||
|
||||
public final class StopVPNWorker extends Worker {
|
||||
|
||||
public StopVPNWorker(
|
||||
Context appContext,
|
||||
WorkerParameters workerParams) {
|
||||
super(appContext, workerParams);
|
||||
}
|
||||
|
||||
@Override public Result doWork() {
|
||||
disconnect();
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
private native void disconnect();
|
||||
}
|
Loading…
Reference in New Issue