Mult proto + new app

This commit is contained in:
2026-08-16 16:10:38 -03:00
parent 1fb431ccba
commit 96fe00eb2b
18 changed files with 2635 additions and 35 deletions
+3
View File
@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml
File diff suppressed because it is too large Load Diff
+10
View File
@@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="build">
<CLASSES>
<root url="jar://$PROJECT_DIR$/build/jclasses.jar!/" />
<root url="jar://$PROJECT_DIR$/build/kclasses.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
+67
View File
@@ -0,0 +1,67 @@
<component name="libraryTable">
<library name="lib">
<CLASSES>
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/allopen-compiler-plugin.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-compiler.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-stdlib-jdk7-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-daemon-client.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-metadata-jvm-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-scripting-compiler-impl.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-annotation-processing.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-test.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-stdlib-jdk8-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/scripting-compiler.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlinx-serialization-compiler-plugin.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-script-runtime-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/mutability-annotations-compat.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-stdlib-js-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-test-testng.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-runner.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-scripting-common.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/trove4j.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-stdlib-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-daemon.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/lombok-compiler-plugin.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/compose-compiler-plugin.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-scripting-jvm.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-main-kts.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-reflect-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-stdlib-jdk8.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-imports-dumper-compiler-plugin.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-test-testng-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-test-junit5.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/annotations-13.0.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-script-runtime.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-test-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-test-js-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/parcelize-compiler.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/assignment-compiler-plugin.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-annotation-processing-compiler.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/sam-with-receiver-compiler-plugin.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-stdlib.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-scripting-compiler.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-test-junit-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/android-extensions-compiler.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/power-assert-compiler-plugin.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-annotations-jvm.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-annotation-processing-cli.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-preloader.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/android-extensions-runtime.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/parcelize-runtime.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-test-junit5-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-annotations-jvm-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-stdlib-jdk7.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-test-junit.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-ant.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-metadata-jvm.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-reflect.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlinx-coroutines-core-jvm.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/noarg-compiler-plugin.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-annotation-processing-runtime.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/kotlin-serialization-compiler-plugin.jar!/" />
<root url="jar://$PROJECT_DIR$/.tools/kotlinc-2.1.21/kotlinc/lib/jvm-abi-gen.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
+5
View File
@@ -0,0 +1,5 @@
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/android.iml" filepath="$PROJECT_DIR$/android.iml" />
</modules>
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>
+18 -3
View File
@@ -1,12 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dragontcp.client"
android:versionCode="13"
android:versionName="13.0-hybrid">
android:versionCode="14"
android:versionName="14.0-hybrid">
<!-- minSdk 24 covers Android 7.0 and later. It was 29, which silently made
the APK uninstallable on every device older than Android 10. -->
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="36" />
<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Required from API 34 for a foreground service of type specialUse. -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<!-- Required from API 33 for the ongoing VPN notification to be visible. -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- 64-bit ARM is the primary target; the other ABIs are shipped so 32-bit
and emulator devices can install too. -->
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<application
android:allowBackup="false"
@@ -36,10 +47,14 @@
<service
android:name=".DragonService"
android:exported="true"
android:foregroundServiceType="specialUse"
android:permission="android.permission.BIND_VPN_SERVICE">
<intent-filter>
<action android:name="android.net.VpnService" />
</intent-filter>
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="VPN tunnel transport kept alive while the device is connected" />
</service>
</application>
</manifest>
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="build" level="project" />
<orderEntry type="library" name="build" level="project" />
<orderEntry type="library" name="build" level="project" />
</component>
</module>
+13 -13
View File
@@ -125,17 +125,14 @@ $platRoot = Join-Path $SdkRoot 'platforms'
if ($Platform) {
$AJ = Join-Path $platRoot "$Platform\android.jar"
} else {
$preferred = Join-Path $platRoot 'android-35\android.jar'
if (Test-Path $preferred) {
$Platform = 'android-35'; $AJ = $preferred
} else {
$p = Get-ChildItem $platRoot -Directory -ErrorAction SilentlyContinue |
Where-Object { Test-Path (Join-Path $_.FullName 'android.jar') } |
Sort-Object { [int]($_.Name -replace '\D', '') } -Descending |
Select-Object -First 1
if (-not $p) { Die "No android.jar found under $platRoot. Install a platform via the SDK Manager." }
$Platform = $p.Name; $AJ = Join-Path $p.FullName 'android.jar'
}
# Compile against the newest installed platform: targetSdk 36 must be built
# against android-36 or aapt rejects the manifest.
$p = Get-ChildItem $platRoot -Directory -ErrorAction SilentlyContinue |
Where-Object { Test-Path (Join-Path $_.FullName 'android.jar') } |
Sort-Object { [int]($_.Name -replace '\D', '') } -Descending |
Select-Object -First 1
if (-not $p) { Die "No android.jar found under $platRoot. Install a platform via the SDK Manager." }
$Platform = $p.Name; $AJ = Join-Path $p.FullName 'android.jar'
}
if (-not (Test-Path $AJ)) { Die "Android platform jar not found: $AJ" }
@@ -232,7 +229,7 @@ if ($BuildCore -or -not (Test-Path $so)) {
$coreScript = Join-Path $RepoRoot 'build_core.ps1'
if (-not (Test-Path $coreScript)) { Die "$why but $coreScript was not found. Pass -RepoRoot." }
Step 'Native core (Go)...'
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $coreScript -ClientOnly -AndroidLibDir $libDir
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $coreScript -ClientOnly -AndroidLibRoot (Join-Path $Root 'lib')
if ($LASTEXITCODE -ne 0) { Die 'build_core.ps1 failed.' }
}
if (-not (Test-Path $so)) { Die "Missing $so - build the Go core first." }
@@ -257,6 +254,9 @@ Invoke-Tool (Join-Path $BT 'aapt.exe') @(
'-S', (Join-Path $Root 'res'),
'-A', (Join-Path $Root 'assets'),
'-I', $AJ,
# From targetSdk 30 Android refuses to install an APK whose resources.arsc
# is compressed, so tell aapt to store it instead.
'-0', 'arsc',
'-F', "$B\resources.ap_"
) 'aapt'
@@ -284,7 +284,7 @@ Invoke-Tool $jar @('cf', "$B\jclasses.jar", '-C', "$B\jclasses", '.') 'jar (java
Step 'DEX...'
Invoke-Tool (Join-Path $BT 'd8.bat') (@(
'--lib', $AJ, '--min-api', '29', '--output', "$B\dex",
'--lib', $AJ, '--min-api', '24', '--output', "$B\dex",
"$B\kclasses.jar", "$B\jclasses.jar", $coro
) + $stdlib) 'd8'
Binary file not shown.
Binary file not shown.
@@ -361,9 +361,10 @@ public class DragonService extends VpnService {
if (process != null) {
try {
process.destroy();
if (!process.waitFor(1200, TimeUnit.MILLISECONDS)) {
// Process.waitFor(long, TimeUnit) is API 26; minSdk is 24.
if (!awaitExit(process, 1200)) {
process.destroyForcibly();
process.waitFor(800, TimeUnit.MILLISECONDS);
awaitExit(process, 800);
}
} catch (Throwable ignored) {
try { process.destroyForcibly(); } catch (Throwable ignored2) {}
@@ -371,6 +372,25 @@ public class DragonService extends VpnService {
}
}
/** Waits up to timeoutMs for the process to exit, without needing API 26. */
private static boolean awaitExit(Process process, long timeoutMs) throws InterruptedException {
long deadline = System.currentTimeMillis() + timeoutMs;
while (System.currentTimeMillis() < deadline) {
try {
process.exitValue();
return true;
} catch (IllegalThreadStateException stillRunning) {
Thread.sleep(50);
}
}
try {
process.exitValue();
return true;
} catch (IllegalThreadStateException stillRunning) {
return false;
}
}
private void stopEverythingLocked(String logMessage) {
if (stopping) return;
stopping = true;
@@ -10,6 +10,7 @@ import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowInsets;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ScrollView;
@@ -127,6 +128,7 @@ public class LogActivity extends Activity {
});
setContentView(root);
applyInsets(root, dp(16), dp(14), dp(16), dp(12));
}
private void appendLogLine(String line) {
@@ -177,4 +179,27 @@ public class LogActivity extends Activity {
private int dp(int value) {
return Math.round(value * getResources().getDisplayMetrics().density);
}
/**
* Pads the root by the system bar insets. Required from API 35, where the
* activity is laid out edge to edge and would otherwise draw under the
* status and navigation bars.
*/
private void applyInsets(final View root, final int l, final int t, final int r, final int b) {
root.setOnApplyWindowInsetsListener((v, insets) -> {
int top, bottom, left, right;
if (Build.VERSION.SDK_INT >= 30) {
android.graphics.Insets bars = insets.getInsets(WindowInsets.Type.systemBars());
top = bars.top; bottom = bars.bottom; left = bars.left; right = bars.right;
} else {
top = insets.getSystemWindowInsetTop();
bottom = insets.getSystemWindowInsetBottom();
left = insets.getSystemWindowInsetLeft();
right = insets.getSystemWindowInsetRight();
}
v.setPadding(l + left, t + top, r + right, b + bottom);
return insets;
});
root.requestApplyInsets();
}
}
@@ -19,6 +19,7 @@ import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowInsets;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
@@ -28,6 +29,7 @@ import android.widget.Toast;
public class MainActivity extends Activity {
private static final int VPN_REQUEST = 100;
private static final int NOTIF_REQUEST = 101;
private static final String PREFS = "dragontcp";
private static final int BATCH_LIMIT = 256;
@@ -86,6 +88,7 @@ public class MainActivity extends Activity {
forceDarkSystemUi();
buildUi();
loadSettings();
ensureNotificationPermission();
updateBatchHint();
}
@@ -93,7 +96,15 @@ public class MainActivity extends Activity {
protected void onStart() {
super.onStart();
if (!receiverRegistered) {
registerReceiver(stateReceiver, new IntentFilter(DragonService.ACTION_STATE));
// From API 33 the export flag is mandatory. This receiver only ever
// hears DragonService's own package-scoped broadcast, so it must be
// NOT_EXPORTED; omitting the flag throws SecurityException.
IntentFilter filter = new IntentFilter(DragonService.ACTION_STATE);
if (Build.VERSION.SDK_INT >= 33) {
registerReceiver(stateReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
} else {
registerReceiver(stateReceiver, filter);
}
receiverRegistered = true;
}
updateConnectionUi(DragonService.isActive(), DragonService.currentStatus());
@@ -110,15 +121,59 @@ public class MainActivity extends Activity {
private void forceDarkSystemUi() {
Window w = getWindow();
w.setStatusBarColor(BG);
w.setNavigationBarColor(Color.rgb(7, 10, 14));
// setStatusBarColor / setNavigationBarColor are no-ops once the app
// targets API 35+, where every activity is laid out edge to edge. The
// window background supplies the colour instead and applyInsets() keeps
// content clear of the system bars.
if (Build.VERSION.SDK_INT < 35) {
w.setStatusBarColor(BG);
w.setNavigationBarColor(Color.rgb(7, 10, 14));
}
if (Build.VERSION.SDK_INT >= 26) w.getDecorView().setSystemUiVisibility(0);
}
/**
* Pads the root view by the system bar insets. Required from API 35: the
* activity draws edge to edge, so without this the header sits under the
* status bar and the buttons under the navigation bar.
*/
private void applyInsets(final View root, final int l, final int t, final int r, final int b) {
root.setOnApplyWindowInsetsListener((v, insets) -> {
int top, bottom, left, right;
if (Build.VERSION.SDK_INT >= 30) {
android.graphics.Insets bars =
insets.getInsets(WindowInsets.Type.systemBars() | WindowInsets.Type.ime());
top = bars.top; bottom = bars.bottom; left = bars.left; right = bars.right;
} else {
top = insets.getSystemWindowInsetTop();
bottom = insets.getSystemWindowInsetBottom();
left = insets.getSystemWindowInsetLeft();
right = insets.getSystemWindowInsetRight();
}
v.setPadding(l + left, t + top, r + right, b + bottom);
return insets;
});
root.requestApplyInsets();
}
/** From API 33 the ongoing VPN notification is hidden without this grant. */
private void ensureNotificationPermission() {
if (Build.VERSION.SDK_INT < 33) return;
if (checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS)
== android.content.pm.PackageManager.PERMISSION_GRANTED) {
return;
}
try {
requestPermissions(new String[]{ android.Manifest.permission.POST_NOTIFICATIONS }, NOTIF_REQUEST);
} catch (Throwable ignored) {
}
}
private void buildUi() {
LinearLayout root = new LinearLayout(this);
root.setOrientation(LinearLayout.VERTICAL);
root.setBackgroundColor(BG);
// Base padding; applyInsets() adds the system bar insets on top.
root.setPadding(dp(16), dp(14), dp(16), dp(12));
LinearLayout header = new LinearLayout(this);
@@ -274,6 +329,7 @@ public class MainActivity extends Activity {
logsButton.setOnClickListener(v -> startActivity(new Intent(this, LogActivity.class)));
setContentView(root);
applyInsets(root, dp(16), dp(14), dp(16), dp(12));
updateConnectionUi(false, "DISCONNECTED");
}