Skip to content

Commit

Permalink
增加磁贴按钮
Browse files Browse the repository at this point in the history
  • Loading branch information
vnt-dev committed Jul 10, 2024
1 parent f57db8a commit 18d9536
Show file tree
Hide file tree
Showing 12 changed files with 541 additions and 178 deletions.
30 changes: 28 additions & 2 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE"
tools:ignore="ProtectedPermissions" />

<application
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
Expand All @@ -14,6 +18,23 @@
<action android:name="android.net.VpnService" />
</intent-filter>
</service>
<service
android:name=".MyTileService"
android:icon="@mipmap/ic_launcher"
android:label="VNT"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
android:exported="true">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
<meta-data
android:name="android.service.quicksettings.ACTIVE_TILE"
android:value="false" />

<meta-data
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
android:value="true" />
</service>
<meta-data
android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON"
android:value="true" />
Expand All @@ -25,7 +46,9 @@
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize">
android:enableOnBackInvokedCallback="false"
android:windowSoftInputMode="adjustResize"
tools:targetApi="tiramisu">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
Expand All @@ -37,6 +60,9 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package top.wherewego.vnt_app;

import android.os.Build;
import android.service.quicksettings.Tile;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;
import top.wherewego.vnt_app.vpn.DeviceConfig;
import top.wherewego.vnt_app.vpn.IpUtils;

public class FlutterMethodChannel {
private static final String CHANNEL = "top.wherewego.vnt/vpn";

private volatile static MethodChannel channel;
private volatile static MethodChannel.Result pendingResult;
private volatile static boolean tileStart = false;

public static void setTileStart(boolean tileStart) {
FlutterMethodChannel.tileStart = tileStart;
}

public static boolean initialized() {
return channel != null;
}

public static void init(FlutterEngine flutterEngine, Callback callback) {
channel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL);
channel.setMethodCallHandler((call, result) -> {
switch (call.method) {
case "startVpn":
DeviceConfig config;
try {
Map<String, Object> arguments = call.arguments();
config = parseDeviceConfig(arguments);
} catch (Exception e) {
result.error("VPN_ERROR", "Invalid DeviceConfig", e);
return;
}
try {
pendingResult = result;
callback.startVpn(config);
} catch (Exception e) {
result.error("VPN_ERROR", e.getMessage(), e);
}

break;
case "stopVpn":
callback.stopVpn();
result.success(null);
break;
case "moveTaskToBack":
callback.moveToBack();
result.success(null);
break;
case "isTileStart":
result.success(tileStart);
break;
default:
result.notImplemented();
break;
}
}
);
}

public static void callSuccess(int fd) {
if (pendingResult != null) {
pendingResult.success(fd);
}
pendingResult = null;
}

public static void callError(String msg, Exception e) {
if (pendingResult != null) {
pendingResult.error("VPN_ERROR", msg, e);
}
pendingResult = null;
}

public static void startVnt(Function<Boolean, Void> function) {
if (channel == null) {
return;
}
channel.invokeMethod("startVnt", null, new MethodChannel.Result() {
@Override
public void success(@Nullable Object result) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
function.apply(Boolean.TRUE.equals(result));
}
}

@Override
public void error(@NonNull String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
Log.e("FlutterChannel", errorMessage);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
function.apply(false);
}
}

@Override
public void notImplemented() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
function.apply(false);
}
}
});
}

public static void stopVnt() {
if (channel == null) {
return;
}
channel.invokeMethod("stopVnt", null);
}

public static void isRunning(Function<Boolean, Void> function) {
if (channel == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
function.apply(false);
}
return;
}
channel.invokeMethod("isRunning", null, new MethodChannel.Result() {
@Override
public void success(@Nullable Object result) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
function.apply(Boolean.TRUE.equals(result));
}
}

@Override
public void error(@NonNull String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
Log.e("FlutterChannel", errorMessage);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
function.apply(false);
}
}

@Override
public void notImplemented() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
function.apply(false);
}
}
});
}

private static DeviceConfig parseDeviceConfig(Map<String, Object> arguments) {
String virtualIp = (String) arguments.get("virtualIp");
String virtualNetmask = (String) arguments.get("virtualNetmask");
String virtualGateway = (String) arguments.get("virtualGateway");
Integer mtu = (Integer) arguments.get("mtu");

List<Map<String, String>> externalRouteList = (List<Map<String, String>>) arguments.get("externalRoute");
List<DeviceConfig.Route> externalRoute = new ArrayList<>();
if (externalRouteList != null) {
for (Map<String, String> route : externalRouteList) {
String destination = route.get("destination");
String netmask = route.get("netmask");
externalRoute.add(new DeviceConfig.Route(IpUtils.ipToInt(destination), IpUtils.ipToInt(netmask)));
}
}
return new DeviceConfig(IpUtils.ipToInt(virtualIp), IpUtils.ipToInt(virtualNetmask), IpUtils.ipToInt(virtualGateway), mtu, externalRoute);
}

public interface Callback {
int startVpn(DeviceConfig config);

void stopVpn();

void moveToBack();
}
}
79 changes: 23 additions & 56 deletions android/app/src/main/java/top/wherewego/vnt_app/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,90 +2,57 @@

import android.content.Intent;
import android.net.VpnService;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;

import androidx.annotation.NonNull;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;
import top.wherewego.vnt_app.vpn.DeviceConfig;
import top.wherewego.vnt_app.vpn.IpUtils;
import top.wherewego.vnt_app.vpn.MyVpnService;

public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "top.wherewego.vnt/vpn";
private static final int VPN_REQUEST_CODE = 1;
private static final String TAG = "MainActivity";
public static MethodChannel channel;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}

@Override
protected void onDestroy() {
super.onDestroy();
MyVpnService.stopVpn();
}

@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
channel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL);
channel.setMethodCallHandler((call, result) -> {
Log.i("moveTaskToBack","moveTaskToBack");
if (call.method.equals("startVpn")) {
try {
Map<String, Object> arguments = call.arguments();
DeviceConfig config = parseDeviceConfig(arguments);
startVpnService(config, result);
} catch (Exception e) {
result.error("VPN_ERROR", "Invalid DeviceConfig", e);
}
} else if (call.method.equals("stopVpn")) {
try {
MyVpnService.stopVpn();
} catch (Exception e) {
result.error("VPN_ERROR", "stopVpn", e);
}
result.success(null);
}else if (call.method.equals("moveTaskToBack")) {
// 将应用移至后台而不是退出应用
moveTaskToBack(true);
result.success(null);
} else {
result.notImplemented();
}
FlutterMethodChannel.init(flutterEngine, new FlutterMethodChannel.Callback() {
@Override
public int startVpn(DeviceConfig config) {
startVpnService(config);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
MyTileService.setState(true);
}
);

}
return 0;
}

private DeviceConfig parseDeviceConfig(Map<String, Object> arguments) {
String virtualIp = (String) arguments.get("virtualIp");
String virtualNetmask = (String) arguments.get("virtualNetmask");
String virtualGateway = (String) arguments.get("virtualGateway");
Integer mtu = (Integer) arguments.get("mtu");
@Override
public void stopVpn() {
MyVpnService.stopVpn();
}

List<Map<String, String>> externalRouteList = (List<Map<String, String>>) arguments.get("externalRoute");
List<DeviceConfig.Route> externalRoute = new ArrayList<>();
if (externalRouteList != null) {
for (Map<String, String> route : externalRouteList) {
String destination = route.get("destination");
String netmask = route.get("netmask");
externalRoute.add(new DeviceConfig.Route(IpUtils.ipToInt(destination), IpUtils.ipToInt(netmask)));
@Override
public void moveToBack() {
moveTaskToBack(true);
}
}
return new DeviceConfig(IpUtils.ipToInt(virtualIp), IpUtils.ipToInt(virtualNetmask), IpUtils.ipToInt(virtualGateway), mtu, externalRoute);
});
}

private void startVpnService(DeviceConfig config, MethodChannel.Result result) {
MyVpnService.pendingResult = result;


private void startVpnService(DeviceConfig config) {
MyVpnService.pendingConfig = config;
Intent intent = VpnService.prepare(this);
if (intent != null) {
Expand All @@ -103,7 +70,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
startService(serviceIntent);
} else {
// 用户拒绝授权,返回错误结果给 Flutter
MyVpnService.callError("User denied VPN authorization", null);
FlutterMethodChannel.callError("User denied VPN authorization", null);
}
}
super.onActivityResult(requestCode, resultCode, data);
Expand Down
Loading

0 comments on commit 18d9536

Please sign in to comment.