[controller] add 'iiab-oa controller' project codebase
heavy customization from SocksTun, greatly appreciated.
|
|
@ -0,0 +1,3 @@
|
||||||
|
.gradle
|
||||||
|
app/.externalNativeBuild
|
||||||
|
app/build
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "app/src/main/jni/hev-socks5-tunnel"]
|
||||||
|
path = app/src/main/jni/hev-socks5-tunnel
|
||||||
|
url = https://github.com/heiher/hev-socks5-tunnel
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AndroidProjectSystem">
|
||||||
|
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,291 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="WizardSettings">
|
||||||
|
<option name="children">
|
||||||
|
<map>
|
||||||
|
<entry key="imageWizard">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="children">
|
||||||
|
<map>
|
||||||
|
<entry key="imageAssetPanel">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="children">
|
||||||
|
<map>
|
||||||
|
<entry key="actionbar">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="children">
|
||||||
|
<map>
|
||||||
|
<entry key="clipArt">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="values">
|
||||||
|
<map>
|
||||||
|
<entry key="color" value="000000" />
|
||||||
|
<entry key="imagePath" value="/tmp/ic_android_black_24dp.xml" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="text">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="values">
|
||||||
|
<map>
|
||||||
|
<entry key="color" value="000000" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="textAsset">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="values">
|
||||||
|
<map>
|
||||||
|
<entry key="color" value="000000" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="launcher">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="children">
|
||||||
|
<map>
|
||||||
|
<entry key="foregroundClipArt">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="values">
|
||||||
|
<map>
|
||||||
|
<entry key="imagePath" value="/tmp/ic_android_black_24dp.xml" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="foregroundImage">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="values">
|
||||||
|
<map>
|
||||||
|
<entry key="color" value="000000" />
|
||||||
|
<entry key="imagePath" value="$PROJECT_DIR$/../iiab-project/IIAB-on-Android-Controller.svg" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="foregroundText">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="values">
|
||||||
|
<map>
|
||||||
|
<entry key="color" value="000000" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="foregroundTextAsset">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="values">
|
||||||
|
<map>
|
||||||
|
<entry key="color" value="000000" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="launcherLegacy">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="children">
|
||||||
|
<map>
|
||||||
|
<entry key="clipArt">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="values">
|
||||||
|
<map>
|
||||||
|
<entry key="color" value="000000" />
|
||||||
|
<entry key="imagePath" value="/tmp/ic_android_black_24dp.xml" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="text">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="values">
|
||||||
|
<map>
|
||||||
|
<entry key="color" value="000000" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="textAsset">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="values">
|
||||||
|
<map>
|
||||||
|
<entry key="color" value="000000" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="notification">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="children">
|
||||||
|
<map>
|
||||||
|
<entry key="clipArt">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="values">
|
||||||
|
<map>
|
||||||
|
<entry key="color" value="000000" />
|
||||||
|
<entry key="imagePath" value="/tmp/ic_android_black_24dp.xml" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="text">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="values">
|
||||||
|
<map>
|
||||||
|
<entry key="color" value="000000" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="textAsset">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="values">
|
||||||
|
<map>
|
||||||
|
<entry key="color" value="000000" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="tvBanner">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="children">
|
||||||
|
<map>
|
||||||
|
<entry key="foregroundText">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="values">
|
||||||
|
<map>
|
||||||
|
<entry key="color" value="000000" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="tvChannel">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="children">
|
||||||
|
<map>
|
||||||
|
<entry key="foregroundClipArt">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="values">
|
||||||
|
<map>
|
||||||
|
<entry key="imagePath" value="/tmp/ic_android_black_24dp.xml" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="foregroundImage">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="values">
|
||||||
|
<map>
|
||||||
|
<entry key="color" value="000000" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="foregroundText">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="values">
|
||||||
|
<map>
|
||||||
|
<entry key="color" value="000000" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="foregroundTextAsset">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="values">
|
||||||
|
<map>
|
||||||
|
<entry key="color" value="000000" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<bytecodeTargetLevel target="21" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="deploymentTargetSelector">
|
||||||
|
<selectionStates>
|
||||||
|
<SelectionState runConfigName="app">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
<DropdownSelection timestamp="2026-03-06T16:09:16.786936459Z">
|
||||||
|
<Target type="DEFAULT_BOOT">
|
||||||
|
<handle>
|
||||||
|
<DeviceId pluginId="PhysicalDevice" identifier="serial=69K7MB899PKJGQBI" />
|
||||||
|
</handle>
|
||||||
|
</Target>
|
||||||
|
</DropdownSelection>
|
||||||
|
<DialogSelection />
|
||||||
|
</SelectionState>
|
||||||
|
<SelectionState runConfigName="New Run Config">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
<DropdownSelection timestamp="2026-03-06T00:03:16.988860344Z">
|
||||||
|
<Target type="DEFAULT_BOOT">
|
||||||
|
<handle>
|
||||||
|
<DeviceId pluginId="PhysicalDevice" identifier="serial=a026a310" />
|
||||||
|
</handle>
|
||||||
|
</Target>
|
||||||
|
</DropdownSelection>
|
||||||
|
<DialogSelection />
|
||||||
|
</SelectionState>
|
||||||
|
</selectionStates>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DeviceTable">
|
||||||
|
<option name="columnSorters">
|
||||||
|
<list>
|
||||||
|
<ColumnSorterState>
|
||||||
|
<option name="column" value="Name" />
|
||||||
|
<option name="order" value="ASCENDING" />
|
||||||
|
</ColumnSorterState>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||||
|
<component name="GradleSettings">
|
||||||
|
<option name="linkedExternalProjectsSettings">
|
||||||
|
<GradleProjectSettings>
|
||||||
|
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||||
|
<option name="modules">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
<option value="$PROJECT_DIR$/app" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</GradleProjectSettings>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="MarkdownSettings">
|
||||||
|
<option name="previewPanelProviderInfo">
|
||||||
|
<ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectMigrations">
|
||||||
|
<option name="MigrateToGradleLocalJavaHome">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectType">
|
||||||
|
<option name="id" value="Android" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RunConfigurationProducerService">
|
||||||
|
<option name="ignoredProducers">
|
||||||
|
<set>
|
||||||
|
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="StudioBotProjectSettings">
|
||||||
|
<option name="shareContext" value="OptedIn" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/app/src/main/jni/hev-socks5-tunnel" vcs="" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/app/src/main/jni/hev-socks5-tunnel/src/core" vcs="" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/app/src/main/jni/hev-socks5-tunnel/third-part/hev-task-system" vcs="" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/app/src/main/jni/hev-socks5-tunnel/third-part/lwip" vcs="" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/app/src/main/jni/hev-socks5-tunnel/third-part/yaml" vcs="" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2023 hev
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
# IIAB-oA Controller
|
||||||
|
|
||||||
|
**IIAB-oA Controller** is a specialized infrastructure component for the **Internet-in-a-Box (IIAB)** ecosystem on Android. It acts as a "Walled Garden" and persistent "Watchdog" designed to keep the Termux environment alive and accessible, even on devices with aggressive power management (e.g., Oppo/ColorOS, MIUI).
|
||||||
|
|
||||||
|
## Core Capabilities
|
||||||
|
|
||||||
|
### 🛡️ Master Watchdog (Supervision Layer)
|
||||||
|
An independent foreground service dedicated to environment stability:
|
||||||
|
|
||||||
|
* **CPU & Wi-Fi Shield**: Prevents the system from putting Termux into Doze mode or disabling the Wi-Fi radio.
|
||||||
|
* **Heartbeat Pulse**: Sends a regulated API signal every 20 seconds to maintain process priority.
|
||||||
|
* **Zero-Config Protection**: Works independently of the VPN tunnel.
|
||||||
|
|
||||||
|
### 🌐 Safe Pocket Web (Network Layer)
|
||||||
|
A high-performance VPN tunnel based on the tun2socks engine:
|
||||||
|
|
||||||
|
* **Friendly URLs**: Routes traffic through internal IIAB services seamlessly.
|
||||||
|
* **Walled Garden**: Ensures a secure, filtered browsing environment.
|
||||||
|
* **Per-App Routing**: Granular control over which applications use the secure tunnel.
|
||||||
|
|
||||||
|
### 🔒 Built-in Security
|
||||||
|
* **Biometric/PIN Lock**: Authentication is strictly required before the Watchdog or VPN can be disabled.
|
||||||
|
* **Safety Check**: Prevents activation if the device lacks a secure lock method (PIN/Pattern/Fingerprint), ensuring the user is never "locked out" of their own settings.
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
This project is a heavily customized spin-off of **[SocksTun](https://github.com/heiher/sockstun)** created by **[heiher](https://github.com/heiher)**.
|
||||||
|
All credit for the core native tunneling engine goes to the original author. This derivative has been re-architected to meet the specific requirements of the IIAB project.
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
* **Current Version**: v0.1.12alpha
|
||||||
|
* **License**: **MIT License** (See [LICENSE](LICENSE) for details).
|
||||||
|
* **Compatibility**: Android 8.0 (API 26) and above.
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
This is a preview and demo published in the hope that it will be useful, but WITHOUT ANY WARRANTY.
|
||||||
|
|
||||||
|
---
|
||||||
|
*Maintained by IIAB Contributors - 2026*
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace "org.iiab.controller"
|
||||||
|
compileSdkVersion 34
|
||||||
|
ndkVersion "26.3.11579264"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "org.iiab.controller"
|
||||||
|
minSdkVersion 24
|
||||||
|
targetSdkVersion 34
|
||||||
|
versionCode 21
|
||||||
|
versionName "v0.1.25alpha-debug"
|
||||||
|
setProperty("archivesBaseName", "$applicationId-$versionName")
|
||||||
|
ndk {
|
||||||
|
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
|
||||||
|
}
|
||||||
|
externalNativeBuild {
|
||||||
|
ndkBuild {
|
||||||
|
arguments "APP_CFLAGS+=-DPKGNAME=org/iiab/controller -DCLSNAME=TProxyService -ffile-prefix-map=${rootDir}=."
|
||||||
|
arguments "APP_LDFLAGS+=-Wl,--build-id=none"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
release {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
signingConfig signingConfigs.release
|
||||||
|
}
|
||||||
|
debug {
|
||||||
|
minifyEnabled false
|
||||||
|
signingConfig signingConfigs.release
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def propsFile = rootProject.file('store.properties')
|
||||||
|
def configName = 'release'
|
||||||
|
|
||||||
|
if (propsFile.exists() && android.signingConfigs.hasProperty(configName)) {
|
||||||
|
def props = new Properties()
|
||||||
|
props.load(new FileInputStream(propsFile))
|
||||||
|
if (props!=null && props.containsKey('storeFile')) {
|
||||||
|
android.signingConfigs[configName].storeFile = rootProject.file(props['storeFile'])
|
||||||
|
android.signingConfigs[configName].storePassword = props['storePassword']
|
||||||
|
android.signingConfigs[configName].keyAlias = props['keyAlias']
|
||||||
|
android.signingConfigs[configName].keyPassword = props['keyPassword']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
ndkBuild {
|
||||||
|
path "src/main/jni/Android.mk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lintOptions {
|
||||||
|
checkReleaseBuilds false
|
||||||
|
// Or, if you prefer, you can continue to check for errors in release builds,
|
||||||
|
// but continue the build even when errors are found:
|
||||||
|
abortOnError false
|
||||||
|
}
|
||||||
|
|
||||||
|
dependenciesInfo {
|
||||||
|
// Disables dependency metadata when building APKs.
|
||||||
|
includeInApk = false
|
||||||
|
// Disables dependency metadata when building Android App Bundles.
|
||||||
|
includeInBundle = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||||
|
implementation 'androidx.biometric:biometric:1.1.0'
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"artifactType": {
|
||||||
|
"type": "APK",
|
||||||
|
"kind": "Directory"
|
||||||
|
},
|
||||||
|
"applicationId": "org.iiab.controller",
|
||||||
|
"variantName": "release",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "SINGLE",
|
||||||
|
"filters": [],
|
||||||
|
"attributes": [],
|
||||||
|
"versionCode": 8,
|
||||||
|
"versionName": "v0.1.14alpha",
|
||||||
|
"outputFile": "org.iiab.controller-v0.1.14alpha-release.apk"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"elementType": "File",
|
||||||
|
"minSdkVersionForDexing": 24
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="org.iiab.controller"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<!-- Android 11+ Package Visibility -->
|
||||||
|
<queries>
|
||||||
|
<package android:name="com.termux" />
|
||||||
|
</queries>
|
||||||
|
|
||||||
|
<application android:label="@string/app_name"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:theme="@style/Theme.IIABController">
|
||||||
|
|
||||||
|
<!-- VPN Service (Network Layer) -->
|
||||||
|
<service android:name=".TProxyService" android:process=":native"
|
||||||
|
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||||
|
android:exported="true"
|
||||||
|
android:foregroundServiceType="specialUse">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.net.VpnService"/>
|
||||||
|
</intent-filter>
|
||||||
|
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||||
|
android:value="VPN service"/>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<!-- Watchdog Service (Keep-Alive Layer) -->
|
||||||
|
<service android:name=".WatchdogService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="specialUse">
|
||||||
|
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||||
|
android:value="Watchdog and Heartbeat"/>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<receiver android:enabled="true" android:name=".ServiceReceiver"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<!-- VPN Recovery Receiver (The Boomerang) -->
|
||||||
|
<receiver android:name=".VpnRecoveryReceiver" android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.iiab.controller.RECOVER_VPN" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<activity android:name=".MainActivity" android:label="@string/app_name"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity android:name=".AppListActivity" android:label="@string/app_name"/>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
|
<uses-permission android:name="com.termux.permission.RUN_COMMAND" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
|
||||||
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
|
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||||
|
tools:ignore="QueryAllPackagesPermission" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
|
||||||
|
android:minSdkVersion="34" />
|
||||||
|
</manifest>
|
||||||
|
After Width: | Height: | Size: 26 KiB |
|
|
@ -0,0 +1,225 @@
|
||||||
|
/*
|
||||||
|
============================================================================
|
||||||
|
Name : AppListActivity.java
|
||||||
|
Author : hev <r@hev.cc>
|
||||||
|
Copyright : Copyright (c) 2025 xyz
|
||||||
|
Description : App List Activity
|
||||||
|
============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.iiab.controller;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.app.ListActivity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
|
||||||
|
public class AppListActivity extends ListActivity {
|
||||||
|
private Preferences prefs;
|
||||||
|
private AppArrayAdapter adapter;
|
||||||
|
private boolean isChanged = false;
|
||||||
|
|
||||||
|
private class Package {
|
||||||
|
public PackageInfo info;
|
||||||
|
public boolean selected;
|
||||||
|
public String label;
|
||||||
|
|
||||||
|
public Package(PackageInfo info, boolean selected, String label) {
|
||||||
|
this.info = info;
|
||||||
|
this.selected = selected;
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AppArrayAdapter extends ArrayAdapter<Package> {
|
||||||
|
private final List<Package> allPackages = new ArrayList<Package>();
|
||||||
|
private final List<Package> filteredPackages = new ArrayList<Package>();
|
||||||
|
private String lastFilter = "";
|
||||||
|
|
||||||
|
public AppArrayAdapter(Context context) {
|
||||||
|
super(context, R.layout.appitem);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(Package pkg) {
|
||||||
|
allPackages.add(pkg);
|
||||||
|
if (matchesFilter(pkg, lastFilter))
|
||||||
|
filteredPackages.add(pkg);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
allPackages.clear();
|
||||||
|
filteredPackages.clear();
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sort(Comparator<? super Package> cmp) {
|
||||||
|
Collections.sort(allPackages, (Comparator) cmp);
|
||||||
|
applyFilter(lastFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return filteredPackages.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Package getItem(int position) {
|
||||||
|
return filteredPackages.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Package> getAllPackages() {
|
||||||
|
return allPackages;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean matchesFilter(Package pkg, String filter) {
|
||||||
|
if (filter == null || filter.length() == 0)
|
||||||
|
return true;
|
||||||
|
return pkg.label.toLowerCase().contains(filter.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applyFilter(String filter) {
|
||||||
|
lastFilter = filter != null ? filter : "";
|
||||||
|
filteredPackages.clear();
|
||||||
|
if (lastFilter.length() == 0) {
|
||||||
|
filteredPackages.addAll(allPackages);
|
||||||
|
} else {
|
||||||
|
String f = lastFilter.toLowerCase();
|
||||||
|
for (Package p : allPackages) {
|
||||||
|
if (p.label != null && p.label.toLowerCase().contains(f))
|
||||||
|
filteredPackages.add(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
LayoutInflater inflater = (LayoutInflater) getContext()
|
||||||
|
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
View rowView = inflater.inflate(R.layout.appitem, parent, false);
|
||||||
|
ImageView imageView = (ImageView) rowView.findViewById(R.id.icon);
|
||||||
|
TextView textView = (TextView) rowView.findViewById(R.id.name);
|
||||||
|
CheckBox checkBox = (CheckBox) rowView.findViewById(R.id.checked);
|
||||||
|
|
||||||
|
Package pkg = getItem(position);
|
||||||
|
PackageManager pm = getContext().getPackageManager();
|
||||||
|
ApplicationInfo appinfo = pkg.info.applicationInfo;
|
||||||
|
imageView.setImageDrawable(appinfo.loadIcon(pm));
|
||||||
|
textView.setText(appinfo.loadLabel(pm).toString());
|
||||||
|
checkBox.setChecked(pkg.selected);
|
||||||
|
|
||||||
|
return rowView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
|
||||||
|
|
||||||
|
prefs = new Preferences(this);
|
||||||
|
Set<String> apps = prefs.getApps();
|
||||||
|
PackageManager pm = getPackageManager();
|
||||||
|
adapter = new AppArrayAdapter(this);
|
||||||
|
|
||||||
|
for (PackageInfo info : pm.getInstalledPackages(PackageManager.GET_PERMISSIONS)) {
|
||||||
|
if (info.packageName.equals(getPackageName()))
|
||||||
|
continue;
|
||||||
|
if (info.requestedPermissions == null)
|
||||||
|
continue;
|
||||||
|
if (!Arrays.asList(info.requestedPermissions).contains(Manifest.permission.INTERNET))
|
||||||
|
continue;
|
||||||
|
boolean selected = apps.contains(info.packageName);
|
||||||
|
String label = info.applicationInfo.loadLabel(pm).toString();
|
||||||
|
Package pkg = new Package(info, selected, label);
|
||||||
|
adapter.add(pkg);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditText searchBox = new EditText(this);
|
||||||
|
searchBox.setHint("Search");
|
||||||
|
int pad = (int) (8 * getResources().getDisplayMetrics().density);
|
||||||
|
searchBox.setPadding(pad, pad, pad, pad);
|
||||||
|
getListView().addHeaderView(searchBox, null, false);
|
||||||
|
|
||||||
|
adapter.sort(new Comparator<Package>() {
|
||||||
|
public int compare(Package a, Package b) {
|
||||||
|
if (a.selected != b.selected)
|
||||||
|
return a.selected ? -1 : 1;
|
||||||
|
return a.label.compareTo(b.label);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setListAdapter(adapter);
|
||||||
|
|
||||||
|
searchBox.addTextChangedListener(new TextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
|
adapter.applyFilter(s.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s) { }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
if (isChanged) {
|
||||||
|
Set<String> apps = new HashSet<String>();
|
||||||
|
|
||||||
|
for (Package pkg : adapter.getAllPackages()) {
|
||||||
|
if (pkg.selected)
|
||||||
|
apps.add(pkg.info.packageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
prefs.setApps(apps);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onListItemClick(ListView l, View v, int position, long id) {
|
||||||
|
int headers = l.getHeaderViewsCount();
|
||||||
|
int adjPos = position - headers;
|
||||||
|
if (adjPos < 0)
|
||||||
|
return;
|
||||||
|
Package pkg = adapter.getItem(adjPos);
|
||||||
|
pkg.selected = !pkg.selected;
|
||||||
|
CheckBox checkbox = (CheckBox) v.findViewById(R.id.checked);
|
||||||
|
if (checkbox != null)
|
||||||
|
checkbox.setChecked(pkg.selected);
|
||||||
|
isChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
package org.iiab.controller;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A stateless utility class to perform keep-alive actions for Termux.
|
||||||
|
* The lifecycle (start/stop/loop) is managed by the calling service.
|
||||||
|
*/
|
||||||
|
public class IIABWatchdog {
|
||||||
|
private static final String TAG = "IIAB-Controller";
|
||||||
|
|
||||||
|
public static final String ACTION_LOG_MESSAGE = "org.iiab.controller.LOG_MESSAGE";
|
||||||
|
public static final String EXTRA_MESSAGE = "org.iiab.controller.EXTRA_MESSAGE";
|
||||||
|
|
||||||
|
// --- TEMPORARY DEBUG FLAGS ---
|
||||||
|
private static final boolean DEBUG_ENABLED = true;
|
||||||
|
private static final String BLACKBOX_FILE = "watchdog_heartbeat_log.txt";
|
||||||
|
// ----------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a full heartbeat pulse: sending stimulus and debug ping.
|
||||||
|
*/
|
||||||
|
public static void performHeartbeat(Context context) {
|
||||||
|
sendStimulus(context);
|
||||||
|
performDebugPing(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a command to Termux to keep it active.
|
||||||
|
* This is the real keep-alive mechanism.
|
||||||
|
* @param context The context to use for sending the intent.
|
||||||
|
*/
|
||||||
|
public static void sendStimulus(Context context) {
|
||||||
|
if (DEBUG_ENABLED) {
|
||||||
|
writeToBlackBox(context, "Sending Intent (true) to Termux to keep it awake...");
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = new Intent("com.termux.service.RUN_COMMAND");
|
||||||
|
intent.setClassName("com.termux", "com.termux.app.RunCommandService");
|
||||||
|
intent.putExtra("com.termux.service.RUN_COMMAND_PATH", "/data/data/com.termux/files/usr/bin/true");
|
||||||
|
intent.putExtra("com.termux.service.RUN_COMMAND_BACKGROUND", true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
context.startService(intent);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (DEBUG_ENABLED) {
|
||||||
|
Log.e(TAG, "[DEBUG_DEEP_SLEEP] Failed to send 'true' command to Termux", e);
|
||||||
|
writeToBlackBox(context, "ERROR sending Intent: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pings the Termux NGINX server to check if it's responsive.
|
||||||
|
* This is a temporary debugging tool.
|
||||||
|
* // TODO: REMOVE AFTER HANS DEBUGGING
|
||||||
|
* @param context The context for writing to the blackbox log.
|
||||||
|
*/
|
||||||
|
public static void performDebugPing(Context context) {
|
||||||
|
final String NGINX_IP = "127.0.0.1";
|
||||||
|
final int NGINX_PORT = 8085;
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
try (Socket socket = new Socket()) {
|
||||||
|
socket.connect(new InetSocketAddress(NGINX_IP, NGINX_PORT), 2000);
|
||||||
|
if (DEBUG_ENABLED) {
|
||||||
|
Log.e(TAG, "[DEBUG_DEEP_SLEEP] PING 8085 SUCCESSFUL: Termux is alive.");
|
||||||
|
writeToBlackBox(context, "PING 8085 SUCCESSFUL: Termux is alive.");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (DEBUG_ENABLED) {
|
||||||
|
Log.w(TAG, "[DEBUG_DEEP_SLEEP] PING 8085 FAILED: " + e.getMessage());
|
||||||
|
writeToBlackBox(context, "PING 8085 FAILED: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void logSessionStart(Context context) {
|
||||||
|
if (DEBUG_ENABLED) {
|
||||||
|
writeToBlackBox(context, "HEARTBEAT SESSION STARTED (Thread based)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void logSessionStop(Context context) {
|
||||||
|
if (DEBUG_ENABLED) {
|
||||||
|
writeToBlackBox(context, "HEARTBEAT SESSION STOPPED");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeToBlackBox(Context context, String message) {
|
||||||
|
try {
|
||||||
|
File logFile = new File(context.getFilesDir(), BLACKBOX_FILE);
|
||||||
|
FileWriter writer = new FileWriter(logFile, true);
|
||||||
|
String datePrefix = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date());
|
||||||
|
writer.append(datePrefix).append(" - ").append(message).append("\n");
|
||||||
|
writer.close();
|
||||||
|
|
||||||
|
// Also broadcast for UI update
|
||||||
|
broadcastLog(context, message);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Failed to write to BlackBox", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void broadcastLog(Context context, String message) {
|
||||||
|
Intent intent = new Intent(ACTION_LOG_MESSAGE);
|
||||||
|
intent.putExtra(EXTRA_MESSAGE, message);
|
||||||
|
context.sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,540 @@
|
||||||
|
/*
|
||||||
|
============================================================================
|
||||||
|
Name : MainActivity.java
|
||||||
|
Author : hev <r@hev.cc>
|
||||||
|
Copyright : Copyright (c) 2023 xyz
|
||||||
|
Description : Main Activity
|
||||||
|
============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.iiab.controller;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.ClipData;
|
||||||
|
import android.content.ClipboardManager;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import android.net.VpnService;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.text.method.ScrollingMovementMethod;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.PowerManager;
|
||||||
|
import android.provider.Settings;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.biometric.BiometricManager;
|
||||||
|
import androidx.biometric.BiometricPrompt;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
|
||||||
|
private Preferences prefs;
|
||||||
|
private EditText edittext_socks_addr;
|
||||||
|
private EditText edittext_socks_udp_addr;
|
||||||
|
private EditText edittext_socks_port;
|
||||||
|
private EditText edittext_socks_user;
|
||||||
|
private EditText edittext_socks_pass;
|
||||||
|
private EditText edittext_dns_ipv4;
|
||||||
|
private EditText edittext_dns_ipv6;
|
||||||
|
private CheckBox checkbox_udp_in_tcp;
|
||||||
|
private CheckBox checkbox_remote_dns;
|
||||||
|
private CheckBox checkbox_global;
|
||||||
|
private CheckBox checkbox_ipv4;
|
||||||
|
private CheckBox checkbox_ipv6;
|
||||||
|
private Button button_apps;
|
||||||
|
private Button button_save;
|
||||||
|
private Button button_control;
|
||||||
|
|
||||||
|
private Button watchdogControl;
|
||||||
|
private TextView connectionLog;
|
||||||
|
private LinearLayout logActions;
|
||||||
|
private Button btnClearLog;
|
||||||
|
private Button btnCopyLog;
|
||||||
|
private LinearLayout configLayout;
|
||||||
|
private TextView configLabel;
|
||||||
|
private LinearLayout advancedConfig;
|
||||||
|
private TextView advConfigLabel;
|
||||||
|
private TextView logLabel;
|
||||||
|
private ImageButton themeToggle;
|
||||||
|
private TextView versionFooter;
|
||||||
|
|
||||||
|
private BroadcastReceiver logReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (IIABWatchdog.ACTION_LOG_MESSAGE.equals(intent.getAction())) {
|
||||||
|
String message = intent.getStringExtra(IIABWatchdog.EXTRA_MESSAGE);
|
||||||
|
addToLog(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
prefs = new Preferences(this);
|
||||||
|
setContentView(R.layout.main);
|
||||||
|
|
||||||
|
edittext_socks_addr = (EditText) findViewById(R.id.socks_addr);
|
||||||
|
edittext_socks_udp_addr = (EditText) findViewById(R.id.socks_udp_addr);
|
||||||
|
edittext_socks_port = (EditText) findViewById(R.id.socks_port);
|
||||||
|
edittext_socks_user = (EditText) findViewById(R.id.socks_user);
|
||||||
|
edittext_socks_pass = (EditText) findViewById(R.id.socks_pass);
|
||||||
|
edittext_dns_ipv4 = (EditText) findViewById(R.id.dns_ipv4);
|
||||||
|
edittext_dns_ipv6 = (EditText) findViewById(R.id.dns_ipv6);
|
||||||
|
checkbox_ipv4 = (CheckBox) findViewById(R.id.ipv4);
|
||||||
|
checkbox_ipv6 = (CheckBox) findViewById(R.id.ipv6);
|
||||||
|
checkbox_global = (CheckBox) findViewById(R.id.global);
|
||||||
|
checkbox_udp_in_tcp = (CheckBox) findViewById(R.id.udp_in_tcp);
|
||||||
|
checkbox_remote_dns = (CheckBox) findViewById(R.id.remote_dns);
|
||||||
|
button_apps = (Button) findViewById(R.id.apps);
|
||||||
|
button_save = (Button) findViewById(R.id.save);
|
||||||
|
button_control = (Button) findViewById(R.id.control);
|
||||||
|
|
||||||
|
watchdogControl = (Button) findViewById(R.id.watchdog_control);
|
||||||
|
watchdogControl.setOnClickListener(this);
|
||||||
|
|
||||||
|
logActions = (LinearLayout) findViewById(R.id.log_actions);
|
||||||
|
btnClearLog = (Button) findViewById(R.id.btn_clear_log);
|
||||||
|
btnCopyLog = (Button) findViewById(R.id.btn_copy_log);
|
||||||
|
btnClearLog.setOnClickListener(this);
|
||||||
|
btnCopyLog.setOnClickListener(this);
|
||||||
|
|
||||||
|
connectionLog = (TextView) findViewById(R.id.connection_log);
|
||||||
|
connectionLog.setMovementMethod(new ScrollingMovementMethod());
|
||||||
|
// Enable text selection for copying large logs
|
||||||
|
connectionLog.setTextIsSelectable(true);
|
||||||
|
|
||||||
|
configLayout = (LinearLayout) findViewById(R.id.config_layout);
|
||||||
|
configLabel = (TextView) findViewById(R.id.config_label);
|
||||||
|
configLabel.setOnClickListener(v -> toggleVisibility(configLayout, configLabel, "Configuration"));
|
||||||
|
|
||||||
|
advancedConfig = (LinearLayout) findViewById(R.id.advanced_config);
|
||||||
|
advConfigLabel = (TextView) findViewById(R.id.adv_config_label);
|
||||||
|
advConfigLabel.setOnClickListener(v -> toggleVisibility(advancedConfig, advConfigLabel, "Advanced Settings"));
|
||||||
|
|
||||||
|
logLabel = (TextView) findViewById(R.id.log_label);
|
||||||
|
logLabel.setOnClickListener(v -> {
|
||||||
|
if (connectionLog.getVisibility() == View.GONE) {
|
||||||
|
readBlackBoxLogs(); // Load logs from file when expanding
|
||||||
|
}
|
||||||
|
toggleVisibility(connectionLog, logLabel, "Connection Log");
|
||||||
|
logActions.setVisibility(connectionLog.getVisibility());
|
||||||
|
});
|
||||||
|
|
||||||
|
themeToggle = (ImageButton) findViewById(R.id.theme_toggle);
|
||||||
|
themeToggle.setOnClickListener(v -> toggleTheme());
|
||||||
|
applySavedTheme();
|
||||||
|
|
||||||
|
versionFooter = (TextView) findViewById(R.id.version_text);
|
||||||
|
setVersionFooter();
|
||||||
|
|
||||||
|
checkbox_udp_in_tcp.setOnClickListener(this);
|
||||||
|
checkbox_remote_dns.setOnClickListener(this);
|
||||||
|
checkbox_global.setOnClickListener(this);
|
||||||
|
button_apps.setOnClickListener(this);
|
||||||
|
button_save.setOnClickListener(this);
|
||||||
|
button_control.setOnClickListener(this);
|
||||||
|
updateUI();
|
||||||
|
|
||||||
|
/* Request VPN permission */
|
||||||
|
Intent intent = VpnService.prepare(MainActivity.this);
|
||||||
|
if (intent != null)
|
||||||
|
startActivityForResult(intent, 0);
|
||||||
|
else
|
||||||
|
onActivityResult(0, RESULT_OK, null);
|
||||||
|
|
||||||
|
checkBatteryOptimizations();
|
||||||
|
addToLog("Application Started");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readBlackBoxLogs() {
|
||||||
|
File logFile = new File(getFilesDir(), "watchdog_heartbeat_log.txt");
|
||||||
|
if (!logFile.exists()) {
|
||||||
|
addToLog("--- No BlackBox file found ---");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addToLog("--- Loading BlackBox Logs ---");
|
||||||
|
try (BufferedReader br = new BufferedReader(new FileReader(logFile))) {
|
||||||
|
String line;
|
||||||
|
while ((line = br.readLine()) != null) {
|
||||||
|
addToLog("[FILE] " + line);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
addToLog("Error reading BlackBox: " + e.getMessage());
|
||||||
|
}
|
||||||
|
addToLog("--- End of File ---");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setVersionFooter() {
|
||||||
|
try {
|
||||||
|
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
|
||||||
|
String version = pInfo.versionName;
|
||||||
|
versionFooter.setText(version);
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
versionFooter.setText("v0.1.x");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
if (getIntent() != null && getIntent().getBooleanExtra(VpnRecoveryReceiver.EXTRA_RECOVERY, false)) {
|
||||||
|
addToLog("Recovery Pulse Received from System. Enforcing VPN...");
|
||||||
|
Intent vpnIntent = new Intent(this, TProxyService.class);
|
||||||
|
startService(vpnIntent.setAction(TProxyService.ACTION_CONNECT));
|
||||||
|
setIntent(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onNewIntent(Intent intent) {
|
||||||
|
super.onNewIntent(intent);
|
||||||
|
setIntent(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkBatteryOptimizations() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||||
|
if (pm != null && !pm.isIgnoringBatteryOptimizations(getPackageName())) {
|
||||||
|
new AlertDialog.Builder(this)
|
||||||
|
.setTitle("Battery Optimization")
|
||||||
|
.setMessage("For the Watchdog to work reliably, please disable battery optimizations for this app.")
|
||||||
|
.setPositiveButton("Go to Settings", (dialog, which) -> {
|
||||||
|
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||||
|
intent.setData(Uri.parse("package:" + getPackageName()));
|
||||||
|
startActivity(intent);
|
||||||
|
})
|
||||||
|
.setNegativeButton("Cancel", null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleTheme() {
|
||||||
|
SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);
|
||||||
|
int currentMode = AppCompatDelegate.getDefaultNightMode();
|
||||||
|
int nextMode;
|
||||||
|
|
||||||
|
if (currentMode == AppCompatDelegate.MODE_NIGHT_NO) {
|
||||||
|
nextMode = AppCompatDelegate.MODE_NIGHT_YES;
|
||||||
|
} else if (currentMode == AppCompatDelegate.MODE_NIGHT_YES) {
|
||||||
|
nextMode = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM;
|
||||||
|
} else {
|
||||||
|
nextMode = AppCompatDelegate.MODE_NIGHT_NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedPreferences.Editor editor = sharedPref.edit();
|
||||||
|
editor.putInt("ui_mode", nextMode);
|
||||||
|
editor.apply();
|
||||||
|
|
||||||
|
AppCompatDelegate.setDefaultNightMode(nextMode);
|
||||||
|
updateThemeToggleButton(nextMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applySavedTheme() {
|
||||||
|
SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);
|
||||||
|
int savedMode = sharedPref.getInt("ui_mode", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
|
||||||
|
AppCompatDelegate.setDefaultNightMode(savedMode);
|
||||||
|
updateThemeToggleButton(savedMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateThemeToggleButton(int mode) {
|
||||||
|
if (mode == AppCompatDelegate.MODE_NIGHT_NO) {
|
||||||
|
themeToggle.setImageResource(R.drawable.ic_theme_dark);
|
||||||
|
} else if (mode == AppCompatDelegate.MODE_NIGHT_YES) {
|
||||||
|
themeToggle.setImageResource(R.drawable.ic_theme_light);
|
||||||
|
} else {
|
||||||
|
themeToggle.setImageResource(R.drawable.ic_theme_system);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleVisibility(View view, TextView label, String text) {
|
||||||
|
if (view.getVisibility() == View.GONE) {
|
||||||
|
view.setVisibility(View.VISIBLE);
|
||||||
|
label.setText("▼ " + text);
|
||||||
|
} else {
|
||||||
|
view.setVisibility(View.GONE);
|
||||||
|
label.setText("▶ " + text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
IntentFilter filter = new IntentFilter(IIABWatchdog.ACTION_LOG_MESSAGE);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
registerReceiver(logReceiver, filter, Context.RECEIVER_EXPORTED);
|
||||||
|
} else {
|
||||||
|
registerReceiver(logReceiver, filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
try {
|
||||||
|
unregisterReceiver(logReceiver);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int request, int result, Intent data) {
|
||||||
|
super.onActivityResult(request, result, data);
|
||||||
|
if ((result == RESULT_OK) && prefs.getEnable()) {
|
||||||
|
Intent intent = new Intent(this, TProxyService.class);
|
||||||
|
startService(intent.setAction(TProxyService.ACTION_CONNECT));
|
||||||
|
addToLog("VPN Permission Granted. Connecting...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
if (view == checkbox_global || view == checkbox_remote_dns) {
|
||||||
|
savePrefs();
|
||||||
|
updateUI();
|
||||||
|
} else if (view == button_apps) {
|
||||||
|
startActivity(new Intent(this, AppListActivity.class));
|
||||||
|
} else if (view == button_save) {
|
||||||
|
savePrefs();
|
||||||
|
Context context = getApplicationContext();
|
||||||
|
Toast.makeText(context, "Saved", Toast.LENGTH_SHORT).show();
|
||||||
|
addToLog("Settings Saved");
|
||||||
|
} else if (view.getId() == R.id.control) {
|
||||||
|
handleControlClick();
|
||||||
|
} else if (view.getId() == R.id.watchdog_control) {
|
||||||
|
handleWatchdogClick();
|
||||||
|
} else if (view.getId() == R.id.btn_clear_log) {
|
||||||
|
connectionLog.setText("");
|
||||||
|
addToLog("Log reset");
|
||||||
|
} else if (view.getId() == R.id.btn_copy_log) {
|
||||||
|
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
|
ClipData clip = ClipData.newPlainText("IIAB Log", connectionLog.getText().toString());
|
||||||
|
if (clipboard != null) {
|
||||||
|
clipboard.setPrimaryClip(clip);
|
||||||
|
Toast.makeText(this, "Log copied to clipboard", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleWatchdogClick() {
|
||||||
|
boolean isEnabled = prefs.getWatchdogEnable();
|
||||||
|
if (isEnabled) {
|
||||||
|
showWatchdogBiometricPrompt();
|
||||||
|
} else {
|
||||||
|
toggleWatchdog(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleWatchdog(boolean stop) {
|
||||||
|
prefs.setWatchdogEnable(!stop);
|
||||||
|
Intent intent = new Intent(this, WatchdogService.class);
|
||||||
|
if (stop) {
|
||||||
|
stopService(intent);
|
||||||
|
addToLog("Watchdog Stopping...");
|
||||||
|
} else {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
startForegroundService(intent.setAction(WatchdogService.ACTION_START));
|
||||||
|
} else {
|
||||||
|
startService(intent.setAction(WatchdogService.ACTION_START));
|
||||||
|
}
|
||||||
|
addToLog("Watchdog Starting...");
|
||||||
|
}
|
||||||
|
updateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleControlClick() {
|
||||||
|
boolean isCurrentlyEnabled = prefs.getEnable();
|
||||||
|
if (isCurrentlyEnabled) {
|
||||||
|
showBiometricPrompt();
|
||||||
|
} else {
|
||||||
|
BiometricManager biometricManager = BiometricManager.from(this);
|
||||||
|
int authenticators = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) ?
|
||||||
|
(BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL) :
|
||||||
|
BiometricManager.Authenticators.BIOMETRIC_WEAK;
|
||||||
|
|
||||||
|
boolean isSecure = false;
|
||||||
|
android.app.KeyguardManager km = (android.app.KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
|
||||||
|
if (km != null && km.isDeviceSecure()) isSecure = true;
|
||||||
|
|
||||||
|
if (biometricManager.canAuthenticate(authenticators) == BiometricManager.BIOMETRIC_SUCCESS || isSecure) {
|
||||||
|
addToLog("User initiated connection");
|
||||||
|
toggleService(false);
|
||||||
|
} else {
|
||||||
|
showEnrollmentDialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showEnrollmentDialog() {
|
||||||
|
new AlertDialog.Builder(this)
|
||||||
|
.setTitle("Security Required")
|
||||||
|
.setMessage("You must set up a PIN, Pattern, or Fingerprint on your device before enabling the secure environment.")
|
||||||
|
.setPositiveButton("Go to Settings", (dialog, which) -> {
|
||||||
|
Intent intent = new Intent(Settings.ACTION_SECURITY_SETTINGS);
|
||||||
|
startActivity(intent);
|
||||||
|
})
|
||||||
|
.setNegativeButton("Cancel", null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showBiometricPrompt() {
|
||||||
|
Executor executor = ContextCompat.getMainExecutor(this);
|
||||||
|
BiometricPrompt biometricPrompt = new BiometricPrompt(MainActivity.this,
|
||||||
|
executor, new BiometricPrompt.AuthenticationCallback() {
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||||
|
super.onAuthenticationSucceeded(result);
|
||||||
|
addToLog("Authentication Success. Disconnecting...");
|
||||||
|
toggleService(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
BiometricPrompt.PromptInfo.Builder promptBuilder = new BiometricPrompt.PromptInfo.Builder()
|
||||||
|
.setTitle("Authentication required")
|
||||||
|
.setSubtitle("Authenticate to disable the secure environment");
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
promptBuilder.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG |
|
||||||
|
BiometricManager.Authenticators.DEVICE_CREDENTIAL);
|
||||||
|
} else {
|
||||||
|
promptBuilder.setDeviceCredentialAllowed(true);
|
||||||
|
}
|
||||||
|
biometricPrompt.authenticate(promptBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showWatchdogBiometricPrompt() {
|
||||||
|
Executor executor = ContextCompat.getMainExecutor(this);
|
||||||
|
BiometricPrompt biometricPrompt = new BiometricPrompt(this, executor, new BiometricPrompt.AuthenticationCallback() {
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||||
|
super.onAuthenticationSucceeded(result);
|
||||||
|
toggleWatchdog(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
BiometricPrompt.PromptInfo.Builder promptBuilder = new BiometricPrompt.PromptInfo.Builder()
|
||||||
|
.setTitle("Unlock Master Watchdog")
|
||||||
|
.setSubtitle("Authentication required to stop Termux protection");
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
promptBuilder.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG |
|
||||||
|
BiometricManager.Authenticators.DEVICE_CREDENTIAL);
|
||||||
|
} else {
|
||||||
|
promptBuilder.setDeviceCredentialAllowed(true);
|
||||||
|
}
|
||||||
|
biometricPrompt.authenticate(promptBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleService(boolean isEnable) {
|
||||||
|
prefs.setEnable(!isEnable);
|
||||||
|
savePrefs();
|
||||||
|
updateUI();
|
||||||
|
Intent intent = new Intent(this, TProxyService.class);
|
||||||
|
if (isEnable) {
|
||||||
|
startService(intent.setAction(TProxyService.ACTION_DISCONNECT));
|
||||||
|
addToLog("VPN Stopping...");
|
||||||
|
} else {
|
||||||
|
startService(intent.setAction(TProxyService.ACTION_CONNECT));
|
||||||
|
addToLog("VPN Starting...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateUI() {
|
||||||
|
boolean vpnActive = prefs.getEnable();
|
||||||
|
boolean watchdogActive = prefs.getWatchdogEnable();
|
||||||
|
|
||||||
|
if (vpnActive) {
|
||||||
|
button_control.setText(R.string.control_disable);
|
||||||
|
button_control.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_vpn_on));
|
||||||
|
} else {
|
||||||
|
button_control.setText(R.string.control_enable);
|
||||||
|
button_control.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_vpn_off));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (watchdogActive) {
|
||||||
|
watchdogControl.setText("Disable Master Watchdog");
|
||||||
|
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_watchdog_on));
|
||||||
|
} else {
|
||||||
|
watchdogControl.setText("Enable Master Watchdog");
|
||||||
|
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_watchdog_off));
|
||||||
|
}
|
||||||
|
|
||||||
|
edittext_socks_addr.setText(prefs.getSocksAddress());
|
||||||
|
edittext_socks_udp_addr.setText(prefs.getSocksUdpAddress());
|
||||||
|
edittext_socks_port.setText(String.valueOf(prefs.getSocksPort()));
|
||||||
|
edittext_socks_user.setText(prefs.getSocksUsername());
|
||||||
|
edittext_socks_pass.setText(prefs.getSocksPassword());
|
||||||
|
edittext_dns_ipv4.setText(prefs.getDnsIpv4());
|
||||||
|
edittext_dns_ipv6.setText(prefs.getDnsIpv6());
|
||||||
|
checkbox_ipv4.setChecked(prefs.getIpv4());
|
||||||
|
checkbox_ipv6.setChecked(prefs.getIpv6());
|
||||||
|
checkbox_global.setChecked(prefs.getGlobal());
|
||||||
|
checkbox_udp_in_tcp.setChecked(prefs.getUdpInTcp());
|
||||||
|
checkbox_remote_dns.setChecked(prefs.getRemoteDns());
|
||||||
|
|
||||||
|
boolean editable = !vpnActive;
|
||||||
|
edittext_socks_addr.setEnabled(editable);
|
||||||
|
edittext_socks_port.setEnabled(editable);
|
||||||
|
button_save.setEnabled(editable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void savePrefs() {
|
||||||
|
prefs.setSocksAddress(edittext_socks_addr.getText().toString());
|
||||||
|
prefs.setSocksPort(Integer.parseInt(edittext_socks_port.getText().toString()));
|
||||||
|
prefs.setSocksUdpAddress(edittext_socks_udp_addr.getText().toString());
|
||||||
|
prefs.setSocksUsername(edittext_socks_user.getText().toString());
|
||||||
|
prefs.setSocksPassword(edittext_socks_pass.getText().toString());
|
||||||
|
prefs.setDnsIpv4(edittext_dns_ipv4.getText().toString());
|
||||||
|
prefs.setDnsIpv6(edittext_dns_ipv6.getText().toString());
|
||||||
|
prefs.setIpv4(checkbox_ipv4.isChecked());
|
||||||
|
prefs.setIpv6(checkbox_ipv6.isChecked());
|
||||||
|
prefs.setGlobal(checkbox_global.isChecked());
|
||||||
|
prefs.setUdpInTcp(checkbox_udp_in_tcp.isChecked());
|
||||||
|
prefs.setRemoteDns(checkbox_remote_dns.isChecked());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addToLog(String message) {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
|
||||||
|
String currentTime = sdf.format(new Date());
|
||||||
|
String logEntry = "[" + currentTime + "] " + message + "\n";
|
||||||
|
if (connectionLog != null) {
|
||||||
|
connectionLog.append(logEntry);
|
||||||
|
// Automatic scrolling to bottom
|
||||||
|
final int scrollAmount = connectionLog.getLayout() != null ?
|
||||||
|
connectionLog.getLayout().getLineTop(connectionLog.getLineCount()) - connectionLog.getHeight() : 0;
|
||||||
|
if (scrollAmount > 0)
|
||||||
|
connectionLog.scrollTo(0, scrollAmount);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,219 @@
|
||||||
|
/*
|
||||||
|
============================================================================
|
||||||
|
Name : Preferences.java
|
||||||
|
Author : hev <r@hev.cc>
|
||||||
|
Copyright : Copyright (c) 2023 xyz
|
||||||
|
Description : Preferences
|
||||||
|
============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.iiab.controller;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
public class Preferences
|
||||||
|
{
|
||||||
|
public static final String PREFS_NAME = "SocksPrefs";
|
||||||
|
public static final String SOCKS_ADDR = "SocksAddr";
|
||||||
|
public static final String SOCKS_UDP_ADDR = "SocksUdpAddr";
|
||||||
|
public static final String SOCKS_PORT = "SocksPort";
|
||||||
|
public static final String SOCKS_USER = "SocksUser";
|
||||||
|
public static final String SOCKS_PASS = "SocksPass";
|
||||||
|
public static final String DNS_IPV4 = "DnsIpv4";
|
||||||
|
public static final String DNS_IPV6 = "DnsIpv6";
|
||||||
|
public static final String IPV4 = "Ipv4";
|
||||||
|
public static final String IPV6 = "Ipv6";
|
||||||
|
public static final String GLOBAL = "Global";
|
||||||
|
public static final String UDP_IN_TCP = "UdpInTcp";
|
||||||
|
public static final String REMOTE_DNS = "RemoteDNS";
|
||||||
|
public static final String APPS = "Apps";
|
||||||
|
public static final String ENABLE = "Enable";
|
||||||
|
public static final String WATCHDOG_ENABLE = "WatchdogEnable";
|
||||||
|
|
||||||
|
private SharedPreferences prefs;
|
||||||
|
|
||||||
|
public Preferences(Context context) {
|
||||||
|
prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_MULTI_PROCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSocksAddress() {
|
||||||
|
return prefs.getString(SOCKS_ADDR, "127.0.0.1");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSocksAddress(String addr) {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putString(SOCKS_ADDR, addr);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSocksUdpAddress() {
|
||||||
|
return prefs.getString(SOCKS_UDP_ADDR, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSocksUdpAddress(String addr) {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putString(SOCKS_UDP_ADDR, addr);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSocksPort() {
|
||||||
|
return prefs.getInt(SOCKS_PORT, 1080);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSocksPort(int port) {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putInt(SOCKS_PORT, port);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSocksUsername() {
|
||||||
|
return prefs.getString(SOCKS_USER, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSocksUsername(String user) {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putString(SOCKS_USER, user);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSocksPassword() {
|
||||||
|
return prefs.getString(SOCKS_PASS, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSocksPassword(String pass) {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putString(SOCKS_PASS, pass);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDnsIpv4() {
|
||||||
|
return prefs.getString(DNS_IPV4, "8.8.8.8");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDnsIpv4(String addr) {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putString(DNS_IPV4, addr);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDnsIpv6() {
|
||||||
|
return prefs.getString(DNS_IPV6, "2001:4860:4860::8888");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDnsIpv6(String addr) {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putString(DNS_IPV6, addr);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMappedDns() {
|
||||||
|
return "198.18.0.2";
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getUdpInTcp() {
|
||||||
|
return prefs.getBoolean(UDP_IN_TCP, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUdpInTcp(boolean enable) {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putBoolean(UDP_IN_TCP, enable);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getRemoteDns() {
|
||||||
|
return prefs.getBoolean(REMOTE_DNS, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRemoteDns(boolean enable) {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putBoolean(REMOTE_DNS, enable);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getIpv4() {
|
||||||
|
return prefs.getBoolean(IPV4, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIpv4(boolean enable) {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putBoolean(IPV4, enable);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getIpv6() {
|
||||||
|
return prefs.getBoolean(IPV6, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIpv6(boolean enable) {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putBoolean(IPV6, enable);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getGlobal() {
|
||||||
|
return prefs.getBoolean(GLOBAL, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGlobal(boolean enable) {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putBoolean(GLOBAL, enable);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getApps() {
|
||||||
|
return prefs.getStringSet(APPS, new HashSet<String>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setApps(Set<String> apps) {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putStringSet(APPS, apps);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getEnable() {
|
||||||
|
return prefs.getBoolean(ENABLE, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnable(boolean enable) {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putBoolean(ENABLE, enable);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getWatchdogEnable() {
|
||||||
|
return prefs.getBoolean(WATCHDOG_ENABLE, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWatchdogEnable(boolean enable) {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putBoolean(WATCHDOG_ENABLE, enable);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTunnelMtu() {
|
||||||
|
return 8500;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTunnelIpv4Address() {
|
||||||
|
return "198.18.0.1";
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTunnelIpv4Prefix() {
|
||||||
|
return 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTunnelIpv6Address() {
|
||||||
|
return "fc00::1";
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTunnelIpv6Prefix() {
|
||||||
|
return 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTaskStackSize() {
|
||||||
|
return 81920;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
============================================================================
|
||||||
|
Name : ServiceReceiver.java
|
||||||
|
Author : hev <r@hev.cc>
|
||||||
|
Copyright : Copyright (c) 2023 xyz
|
||||||
|
Description : ServiceReceiver
|
||||||
|
============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.iiab.controller;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.VpnService;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
public class ServiceReceiver extends BroadcastReceiver {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
|
||||||
|
Preferences prefs = new Preferences(context);
|
||||||
|
|
||||||
|
/* Auto-start */
|
||||||
|
if (prefs.getEnable()) {
|
||||||
|
Intent i = VpnService.prepare(context);
|
||||||
|
if (i != null) {
|
||||||
|
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
context.startActivity(i);
|
||||||
|
}
|
||||||
|
i = new Intent(context, TProxyService.class);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
context.startForegroundService(i.setAction(TProxyService.ACTION_CONNECT));
|
||||||
|
} else {
|
||||||
|
context.startService(i.setAction(TProxyService.ACTION_CONNECT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,337 @@
|
||||||
|
/*
|
||||||
|
============================================================================
|
||||||
|
Name : TProxyService.java
|
||||||
|
Author : hev <r@hev.cc>
|
||||||
|
Copyright : Copyright (c) 2024 xyz
|
||||||
|
Description : TProxy Service
|
||||||
|
============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.iiab.controller;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.os.PowerManager;
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.VpnService;
|
||||||
|
import android.net.wifi.WifiManager;
|
||||||
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
|
import android.content.pm.ServiceInfo;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
|
||||||
|
public class TProxyService extends VpnService {
|
||||||
|
private static final String TAG = "IIAB-TProxy";
|
||||||
|
private static native void TProxyStartService(String config_path, int fd);
|
||||||
|
private static native void TProxyStopService();
|
||||||
|
private static native long[] TProxyGetStats();
|
||||||
|
|
||||||
|
public static final String ACTION_CONNECT = "org.iiab.controller.CONNECT";
|
||||||
|
public static final String ACTION_DISCONNECT = "org.iiab.controller.DISCONNECT";
|
||||||
|
public static final String ACTION_WATCHDOG_SYNC = "org.iiab.controller.WATCHDOG_SYNC";
|
||||||
|
|
||||||
|
private PowerManager.WakeLock wakeLock;
|
||||||
|
private WifiManager.WifiLock wifiLock;
|
||||||
|
|
||||||
|
private Thread watchdogThread;
|
||||||
|
private volatile boolean isWatchdogRunning = false;
|
||||||
|
|
||||||
|
static {
|
||||||
|
System.loadLibrary("hev-socks5-tunnel");
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParcelFileDescriptor tunFd = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
if (intent != null) {
|
||||||
|
String action = intent.getAction();
|
||||||
|
if (ACTION_DISCONNECT.equals(action)) {
|
||||||
|
stopService();
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
} else if (ACTION_WATCHDOG_SYNC.equals(action)) {
|
||||||
|
syncWatchdogLocks();
|
||||||
|
return START_STICKY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
startService();
|
||||||
|
return START_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncWatchdogLocks() {
|
||||||
|
Preferences prefs = new Preferences(this);
|
||||||
|
boolean watchdogEnabled = prefs.getWatchdogEnable();
|
||||||
|
Log.d(TAG, "Syncing Watchdog state. Enabled: " + watchdogEnabled);
|
||||||
|
|
||||||
|
if (watchdogEnabled) {
|
||||||
|
acquireLocks();
|
||||||
|
startWatchdogLoop();
|
||||||
|
} else {
|
||||||
|
stopWatchdogLoop();
|
||||||
|
releaseLocks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startWatchdogLoop() {
|
||||||
|
if (isWatchdogRunning) return;
|
||||||
|
|
||||||
|
isWatchdogRunning = true;
|
||||||
|
IIABWatchdog.logSessionStart(this);
|
||||||
|
|
||||||
|
watchdogThread = new Thread(() -> {
|
||||||
|
Log.i(TAG, "Watchdog Thread: Started loop");
|
||||||
|
while (isWatchdogRunning) {
|
||||||
|
try {
|
||||||
|
// Perform the stimulus (The real keep-alive)
|
||||||
|
IIABWatchdog.sendStimulus(this);
|
||||||
|
|
||||||
|
// Perform the debug ping (Checking if Termux is responsive)
|
||||||
|
IIABWatchdog.performDebugPing(this);
|
||||||
|
|
||||||
|
// Sleep for 30 seconds
|
||||||
|
Thread.sleep(30000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.i(TAG, "Watchdog Thread: Interrupted, stopping...");
|
||||||
|
break;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Watchdog Thread: Error in loop", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.i(TAG, "Watchdog Thread: Loop ended");
|
||||||
|
});
|
||||||
|
watchdogThread.setName("IIAB-Watchdog-Thread");
|
||||||
|
watchdogThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopWatchdogLoop() {
|
||||||
|
isWatchdogRunning = false;
|
||||||
|
if (watchdogThread != null) {
|
||||||
|
watchdogThread.interrupt();
|
||||||
|
watchdogThread = null;
|
||||||
|
}
|
||||||
|
IIABWatchdog.logSessionStop(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void acquireLocks() {
|
||||||
|
try {
|
||||||
|
if (wakeLock == null) {
|
||||||
|
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||||
|
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "IIAB:TProxyWakeLock");
|
||||||
|
wakeLock.acquire();
|
||||||
|
Log.i(TAG, "CPU WakeLock acquired under VPN shield");
|
||||||
|
}
|
||||||
|
if (wifiLock == null) {
|
||||||
|
WifiManager wm = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||||
|
wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "IIAB:TProxyWifiLock");
|
||||||
|
wifiLock.acquire();
|
||||||
|
Log.i(TAG, "Wi-Fi Lock acquired under VPN shield");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error acquiring locks", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseLocks() {
|
||||||
|
if (wakeLock != null && wakeLock.isHeld()) {
|
||||||
|
wakeLock.release();
|
||||||
|
wakeLock = null;
|
||||||
|
Log.i(TAG, "CPU WakeLock released");
|
||||||
|
}
|
||||||
|
if (wifiLock != null && wifiLock.isHeld()) {
|
||||||
|
wifiLock.release();
|
||||||
|
wifiLock = null;
|
||||||
|
Log.i(TAG, "Wi-Fi Lock released");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
stopWatchdogLoop();
|
||||||
|
releaseLocks();
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRevoke() {
|
||||||
|
super.onRevoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startService() {
|
||||||
|
if (tunFd != null) {
|
||||||
|
syncWatchdogLocks();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Preferences prefs = new Preferences(this);
|
||||||
|
|
||||||
|
/* VPN */
|
||||||
|
String session = new String();
|
||||||
|
VpnService.Builder builder = new VpnService.Builder();
|
||||||
|
builder.setBlocking(false);
|
||||||
|
builder.setMtu(prefs.getTunnelMtu());
|
||||||
|
if (prefs.getIpv4()) {
|
||||||
|
String addr = prefs.getTunnelIpv4Address();
|
||||||
|
int prefix = prefs.getTunnelIpv4Prefix();
|
||||||
|
String dns = prefs.getDnsIpv4();
|
||||||
|
builder.addAddress(addr, prefix);
|
||||||
|
builder.addRoute("0.0.0.0", 0);
|
||||||
|
if (!prefs.getRemoteDns() && !dns.isEmpty())
|
||||||
|
builder.addDnsServer(dns);
|
||||||
|
session += "IPv4";
|
||||||
|
}
|
||||||
|
if (prefs.getIpv6()) {
|
||||||
|
String addr = prefs.getTunnelIpv6Address();
|
||||||
|
int prefix = prefs.getTunnelIpv6Prefix();
|
||||||
|
String dns = prefs.getDnsIpv6();
|
||||||
|
builder.addAddress(addr, prefix);
|
||||||
|
builder.addRoute("::", 0);
|
||||||
|
if (!prefs.getRemoteDns() && !dns.isEmpty())
|
||||||
|
builder.addDnsServer(dns);
|
||||||
|
if (!session.isEmpty())
|
||||||
|
session += " + ";
|
||||||
|
session += "IPv6";
|
||||||
|
}
|
||||||
|
if (prefs.getRemoteDns()) {
|
||||||
|
builder.addDnsServer(prefs.getMappedDns());
|
||||||
|
}
|
||||||
|
boolean disallowSelf = true;
|
||||||
|
if (prefs.getGlobal()) {
|
||||||
|
session += "/Global";
|
||||||
|
} else {
|
||||||
|
for (String appName : prefs.getApps()) {
|
||||||
|
try {
|
||||||
|
builder.addAllowedApplication(appName);
|
||||||
|
disallowSelf = false;
|
||||||
|
} catch (NameNotFoundException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
session += "/per-App";
|
||||||
|
}
|
||||||
|
if (disallowSelf) {
|
||||||
|
String selfName = getApplicationContext().getPackageName();
|
||||||
|
try {
|
||||||
|
builder.addDisallowedApplication(selfName);
|
||||||
|
} catch (NameNotFoundException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.setSession(session);
|
||||||
|
tunFd = builder.establish();
|
||||||
|
if (tunFd == null) {
|
||||||
|
stopSelf();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TProxy */
|
||||||
|
File tproxy_file = new File(getCacheDir(), "tproxy.conf");
|
||||||
|
try {
|
||||||
|
tproxy_file.createNewFile();
|
||||||
|
FileOutputStream fos = new FileOutputStream(tproxy_file, false);
|
||||||
|
|
||||||
|
String tproxy_conf = "misc:\n" +
|
||||||
|
" task-stack-size: " + prefs.getTaskStackSize() + "\n" +
|
||||||
|
"tunnel:\n" +
|
||||||
|
" mtu: " + prefs.getTunnelMtu() + "\n";
|
||||||
|
|
||||||
|
tproxy_conf += "socks5:\n" +
|
||||||
|
" port: " + prefs.getSocksPort() + "\n" +
|
||||||
|
" address: '" + prefs.getSocksAddress() + "'\n" +
|
||||||
|
" udp: '" + (prefs.getUdpInTcp() ? "tcp" : "udp") + "'\n";
|
||||||
|
|
||||||
|
if (!prefs.getSocksUdpAddress().isEmpty()) {
|
||||||
|
tproxy_conf += " udp-address: '" + prefs.getSocksUdpAddress() + "'\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prefs.getSocksUsername().isEmpty() &&
|
||||||
|
!prefs.getSocksPassword().isEmpty()) {
|
||||||
|
tproxy_conf += " username: '" + prefs.getSocksUsername() + "'\n";
|
||||||
|
tproxy_conf += " password: '" + prefs.getSocksPassword() + "'\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefs.getRemoteDns()) {
|
||||||
|
tproxy_conf += "mapdns:\n" +
|
||||||
|
" address: " + prefs.getMappedDns() + "\n" +
|
||||||
|
" port: 53\n" +
|
||||||
|
" network: 240.0.0.0\n" +
|
||||||
|
" netmask: 240.0.0.0\n" +
|
||||||
|
" cache-size: 10000\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
fos.write(tproxy_conf.getBytes());
|
||||||
|
fos.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TProxyStartService(tproxy_file.getAbsolutePath(), tunFd.getFd());
|
||||||
|
prefs.setEnable(true);
|
||||||
|
|
||||||
|
String channelName = "socks5";
|
||||||
|
initNotificationChannel(channelName);
|
||||||
|
createNotification(channelName);
|
||||||
|
|
||||||
|
// Start loop and locks if enabled
|
||||||
|
syncWatchdogLocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopService() {
|
||||||
|
if (tunFd == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
stopWatchdogLoop();
|
||||||
|
releaseLocks();
|
||||||
|
stopForeground(true);
|
||||||
|
|
||||||
|
/* TProxy */
|
||||||
|
TProxyStopService();
|
||||||
|
|
||||||
|
/* VPN */
|
||||||
|
try {
|
||||||
|
tunFd.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
tunFd = null;
|
||||||
|
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createNotification(String channelName) {
|
||||||
|
Intent i = new Intent(this, MainActivity.class);
|
||||||
|
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
PendingIntent pi = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_IMMUTABLE);
|
||||||
|
NotificationCompat.Builder notification = new NotificationCompat.Builder(this, channelName);
|
||||||
|
Notification notify = notification
|
||||||
|
.setContentTitle(getString(R.string.app_name))
|
||||||
|
.setSmallIcon(android.R.drawable.sym_def_app_icon)
|
||||||
|
.setContentIntent(pi)
|
||||||
|
.build();
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
|
startForeground(1, notify);
|
||||||
|
} else {
|
||||||
|
startForeground(1, notify, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create NotificationChannel
|
||||||
|
private void initNotificationChannel(String channelName) {
|
||||||
|
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
CharSequence name = getString(R.string.app_name);
|
||||||
|
NotificationChannel channel = new NotificationChannel(channelName, name, NotificationManager.IMPORTANCE_DEFAULT);
|
||||||
|
notificationManager.createNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
package org.iiab.controller;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
|
||||||
|
public class VpnRecoveryReceiver extends BroadcastReceiver {
|
||||||
|
private static final String TAG = "IIAB-VpnRecovery";
|
||||||
|
public static final String EXTRA_RECOVERY = "recovery_mode";
|
||||||
|
private static final String CHANNEL_ID = "recovery_channel";
|
||||||
|
private static final int NOTIFICATION_ID = 911;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if ("org.iiab.controller.RECOVER_VPN".equals(intent.getAction())) {
|
||||||
|
Log.i(TAG, "Boomerang Signal Received! Triggering high-priority recovery...");
|
||||||
|
|
||||||
|
Preferences prefs = new Preferences(context);
|
||||||
|
if (prefs.getEnable()) {
|
||||||
|
showRecoveryNotification(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showRecoveryNotification(Context context) {
|
||||||
|
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
NotificationChannel channel = new NotificationChannel(
|
||||||
|
CHANNEL_ID, "VPN Recovery",
|
||||||
|
NotificationManager.IMPORTANCE_HIGH
|
||||||
|
);
|
||||||
|
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
|
||||||
|
if (manager != null) manager.createNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent uiIntent = new Intent(context, MainActivity.class);
|
||||||
|
uiIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
uiIntent.putExtra(EXTRA_RECOVERY, true);
|
||||||
|
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getActivity(
|
||||||
|
context, 0, uiIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
||||||
|
);
|
||||||
|
|
||||||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
|
.setSmallIcon(android.R.drawable.ic_dialog_alert)
|
||||||
|
.setContentTitle("Safe Pocket Web Interrupted")
|
||||||
|
.setContentText("Tap to restore secure environment immediately.")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setCategory(NotificationCompat.CATEGORY_ALARM)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setOngoing(true)
|
||||||
|
.setFullScreenIntent(pendingIntent, true) // High priority request to open
|
||||||
|
.setContentIntent(pendingIntent);
|
||||||
|
|
||||||
|
if (manager != null) {
|
||||||
|
manager.notify(NOTIFICATION_ID, builder.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
package org.iiab.controller;
|
||||||
|
|
||||||
|
import android.app.AlarmManager;
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.util.Log;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
|
||||||
|
public class WatchdogService extends Service {
|
||||||
|
private static final String CHANNEL_ID = "watchdog_channel";
|
||||||
|
private static final int NOTIFICATION_ID = 2;
|
||||||
|
|
||||||
|
public static final String ACTION_START = "org.iiab.controller.WATCHDOG_START";
|
||||||
|
public static final String ACTION_STOP = "org.iiab.controller.WATCHDOG_STOP";
|
||||||
|
public static final String ACTION_HEARTBEAT = "org.iiab.controller.HEARTBEAT";
|
||||||
|
|
||||||
|
private static final int HEARTBEAT_INTERVAL_MS = 20 * 1000;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
createNotificationChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
if (intent != null) {
|
||||||
|
String action = intent.getAction();
|
||||||
|
if (ACTION_START.equals(action)) {
|
||||||
|
startWatchdog();
|
||||||
|
} else if (ACTION_STOP.equals(action)) {
|
||||||
|
stopWatchdog();
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
} else if (ACTION_HEARTBEAT.equals(action)) {
|
||||||
|
IIABWatchdog.performHeartbeat(this);
|
||||||
|
// CRITICAL: Reschedule for the next pulse to create an infinite loop
|
||||||
|
scheduleHeartbeat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return START_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startWatchdog() {
|
||||||
|
Notification notification = createNotification();
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
startForeground(NOTIFICATION_ID, notification);
|
||||||
|
} else {
|
||||||
|
startForeground(NOTIFICATION_ID, notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
IIABWatchdog.logSessionStart(this);
|
||||||
|
scheduleHeartbeat();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopWatchdog() {
|
||||||
|
cancelHeartbeat();
|
||||||
|
IIABWatchdog.logSessionStop(this);
|
||||||
|
stopForeground(true);
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
private PendingIntent getHeartbeatPendingIntent() {
|
||||||
|
Intent intent = new Intent(this, WatchdogService.class);
|
||||||
|
intent.setAction(ACTION_HEARTBEAT);
|
||||||
|
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
flags |= PendingIntent.FLAG_IMMUTABLE;
|
||||||
|
}
|
||||||
|
return PendingIntent.getService(this, 0, intent, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleHeartbeat() {
|
||||||
|
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
||||||
|
PendingIntent pendingIntent = getHeartbeatPendingIntent();
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
// This wakes up the device even in Doze Mode
|
||||||
|
alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
||||||
|
SystemClock.elapsedRealtime() + HEARTBEAT_INTERVAL_MS,
|
||||||
|
pendingIntent);
|
||||||
|
} else {
|
||||||
|
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
||||||
|
SystemClock.elapsedRealtime() + HEARTBEAT_INTERVAL_MS,
|
||||||
|
pendingIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cancelHeartbeat() {
|
||||||
|
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
||||||
|
PendingIntent pendingIntent = getHeartbeatPendingIntent();
|
||||||
|
if (alarmManager != null) {
|
||||||
|
alarmManager.cancel(pendingIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
stopWatchdog();
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createNotificationChannel() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
NotificationChannel channel = new NotificationChannel(
|
||||||
|
CHANNEL_ID, "IIAB Watchdog Service",
|
||||||
|
NotificationManager.IMPORTANCE_HIGH
|
||||||
|
);
|
||||||
|
channel.setDescription("Ensures services remain active when screen is off.");
|
||||||
|
NotificationManager manager = getSystemService(NotificationManager.class);
|
||||||
|
if (manager != null) {
|
||||||
|
manager.createNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Notification createNotification() {
|
||||||
|
Intent notificationIntent = new Intent(this, MainActivity.class);
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent,
|
||||||
|
PendingIntent.FLAG_IMMUTABLE);
|
||||||
|
|
||||||
|
return new NotificationCompat.Builder(this, CHANNEL_ID)
|
||||||
|
.setContentTitle("IIAB Watchdog Active")
|
||||||
|
.setContentText("Protecting Termux environment...")
|
||||||
|
.setSmallIcon(android.R.drawable.ic_lock_idle_lock)
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setOngoing(true)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Copyright (C) 2023 The Android Open Source Project
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
include $(call all-subdir-makefiles)
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Copyright (C) 2023 The Android Open Source Project
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
APP_OPTIM := release
|
||||||
|
APP_PLATFORM := android-29
|
||||||
|
APP_ABI := armeabi-v7a arm64-v8a
|
||||||
|
APP_CFLAGS := -O3 -DPKGNAME=hev/sockstun
|
||||||
|
APP_CPPFLAGS := -O3 -std=c++11
|
||||||
|
NDK_TOOLCHAIN_VERSION := clang
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 4d6c334dbfb68a79d1970c2744e62d09f71df12f
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector
|
||||||
|
android:height="108dp"
|
||||||
|
android:width="108dp"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#3DDC84"
|
||||||
|
android:pathData="M0,0h108v108h-108z"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="135.47"
|
||||||
|
android:viewportHeight="135.47">
|
||||||
|
<path
|
||||||
|
android:pathData="M0.23,0.23h135v135h-135z"
|
||||||
|
android:strokeWidth="0.462333"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:strokeColor="#000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m39.63,90.18h1.56q-0.18,1.68 -1.27,2.8 -1.09,1.12 -3.22,1.12 -2.06,0 -3.33,-1.47 -1.26,-1.48 -1.28,-3.93v-1.27q0,-2.49 1.28,-3.99 1.28,-1.5 3.48,-1.5 2.01,0 3.09,1.11 1.08,1.11 1.26,2.85h-1.56q-0.18,-1.24 -0.8,-1.95 -0.62,-0.72 -1.99,-0.72 -1.57,0 -2.38,1.15 -0.81,1.15 -0.81,3.04v1.2q0,1.75 0.74,2.98 0.74,1.23 2.32,1.23 1.5,0 2.11,-0.7 0.62,-0.7 0.82,-1.95zM42.69,89.45q0,-1.91 1.07,-3.19 1.07,-1.28 2.92,-1.28 1.85,0 2.92,1.26 1.07,1.25 1.1,3.13v0.27q0,1.91 -1.08,3.19 -1.07,1.28 -2.92,1.28 -1.85,0 -2.93,-1.28 -1.07,-1.28 -1.07,-3.19zM44.2,89.64q0,1.31 0.62,2.27 0.63,0.96 1.89,0.96 1.23,0 1.85,-0.94 0.63,-0.95 0.63,-2.26v-0.21q0,-1.29 -0.63,-2.26 -0.63,-0.98 -1.88,-0.98 -1.24,0 -1.87,0.98 -0.62,0.97 -0.62,2.26zM56.24,86.25q-0.72,0 -1.28,0.39 -0.55,0.39 -0.87,1.02v6.28h-1.5v-8.8h1.42l0.05,1.1q1,-1.26 2.63,-1.26 1.29,0 2.05,0.72 0.76,0.72 0.77,2.43v5.8h-1.51v-5.78q0,-1.03 -0.46,-1.47 -0.45,-0.44 -1.3,-0.44zM65.51,93.94q-0.51,0.16 -1.16,0.16 -0.84,0 -1.43,-0.51 -0.59,-0.51 -0.59,-1.84v-5.45h-1.61v-1.15h1.61L62.32,83.01h1.5v2.14h1.64v1.15h-1.64v5.46q0,0.67 0.29,0.86 0.29,0.19 0.67,0.19 0.28,0 0.71,-0.1zM70.74,86.43q-1.47,0 -2.01,1.27v6.24h-1.5v-8.8h1.46l0.03,1.01q0.72,-1.17 2.09,-1.17 0.42,0 0.67,0.11l-0.01,1.4q-0.33,-0.07 -0.73,-0.07zM72.31,89.45q0,-1.91 1.07,-3.19 1.07,-1.28 2.92,-1.28 1.85,0 2.92,1.26 1.07,1.25 1.1,3.13v0.27q0,1.91 -1.08,3.19 -1.07,1.28 -2.92,1.28 -1.85,0 -2.93,-1.28 -1.07,-1.28 -1.07,-3.19zM73.81,89.64q0,1.31 0.62,2.27 0.63,0.96 1.89,0.96 1.23,0 1.85,-0.94 0.63,-0.95 0.63,-2.26v-0.21q0,-1.29 -0.63,-2.26 -0.63,-0.98 -1.88,-0.98 -1.24,0 -1.87,0.98 -0.62,0.97 -0.62,2.26zM83.84,81.45v12.49h-1.51v-12.49zM87.89,81.45v12.49h-1.51v-12.49zM97.26,92.4q-0.43,0.65 -1.22,1.18 -0.79,0.52 -2.09,0.52 -1.84,0 -2.94,-1.2 -1.1,-1.2 -1.1,-3.06v-0.34q0,-1.44 0.54,-2.45 0.55,-1.02 1.43,-1.54 0.88,-0.54 1.87,-0.54 1.89,0 2.75,1.24 0.87,1.23 0.87,3.07v0.67h-5.95q0.03,1.21 0.72,2.06 0.69,0.85 1.89,0.85 0.8,0 1.35,-0.33 0.55,-0.33 0.97,-0.87zM93.75,86.22q-0.89,0 -1.51,0.65 -0.62,0.65 -0.77,1.87h4.4v-0.11q-0.06,-0.88 -0.51,-1.64 -0.45,-0.76 -1.6,-0.76zM102.64,86.43q-1.47,0 -2.01,1.27v6.24h-1.5v-8.8h1.46l0.03,1.01q0.72,-1.17 2.09,-1.17 0.42,0 0.67,0.11l-0.01,1.4q-0.33,-0.07 -0.73,-0.07z"
|
||||||
|
android:strokeWidth="0.491597"
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:strokeColor="#ffffff"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M29.74,50.22L29.74,68.31L26.02,68.31L26.02,50.22ZM37.17,50.22L37.17,68.31L33.44,68.31L33.44,50.22ZM39.34,68.31 L46.08,50.22h3.45l6.77,18.08h-3.97l-1.24,-3.73h-6.55l-1.24,3.73zM45.54,61.56h4.53l-2.26,-6.79zM71.43,62.98q0,2.61 -1.66,3.96 -1.66,1.34 -4.74,1.37L57.99,68.31L57.99,50.22h6.35q3.14,0 4.92,1.2 1.78,1.2 1.78,3.74 0,1.23 -0.62,2.26 -0.62,1.03 -1.96,1.56 1.59,0.4 2.29,1.54 0.7,1.14 0.7,2.45zM61.72,53.24v4.55h2.61q2.96,0 2.96,-2.24 0,-2.26 -2.83,-2.31zM67.7,62.94q0,-2.43 -2.46,-2.52h-3.51v4.88h3.18q1.43,0 2.11,-0.67 0.68,-0.67 0.68,-1.69z"
|
||||||
|
android:strokeWidth="0.751077"
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:strokeColor="#ffffff"/>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M71.83,38.37L111.31,38.37L111.31,78.26L71.83,78.26ZM99.85,58.32a8.21,8.28 90,0 0,-8.28 -8.21,8.21 8.28,90 0,0 -8.28,8.21 8.21,8.28 90,0 0,8.28 8.21,8.21 8.28,90 0,0 8.28,-8.21z"/>
|
||||||
|
<path
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:pathData="m91.57,39.86a18.46,19.44 90,0 0,-3.37 0.29L86.98,45.84A13.23,13.93 90,0 0,82.48 48.31L76.69,46.46A18.46,19.44 90,0 0,73.31 52.01l4.58,3.84a13.23,13.93 90,0 0,-0.25 2.47,13.23 13.93,90 0,0 0.25,2.47l-4.58,3.84a18.46,19.44 90,0 0,3.37 5.55l5.79,-1.84a13.23,13.93 90,0 0,4.5 2.47l1.21,5.68a18.46,19.44 90,0 0,3.37 0.29,18.46 19.44,90 0,0 3.37,-0.29l1.21,-5.68a13.23,13.93 90,0 0,4.51 -2.47l5.78,1.84a18.46,19.44 90,0 0,3.37 -5.55l-4.57,-3.84a13.23,13.93 90,0 0,0.26 -2.47,13.23 13.93,90 0,0 -0.25,-2.48L109.83,52A18.46,19.44 90,0 0,106.46 46.46L100.67,48.3A13.23,13.93 90,0 0,96.15 45.82l-1.21,-5.68a18.46,19.44 90,0 0,-3.37 -0.28z"
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:strokeColor="#00000000"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="#FFFFFF">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9 9,-4.03 9,-9c0,-0.46 -0.04,-0.92 -0.1,-1.36 -0.98,1.37 -2.58,2.26 -4.4,2.26 -3.03,0 -5.5,-2.47 -5.5,-5.5 0,-1.82 0.89,-3.42 2.26,-4.4C12.92,3.04 12.46,3 12,3z" />
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="#FFFFFF">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5 5,-2.24 5,-5 -2.24,-5 -5,-5zM2,13h2c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1L2,11c-0.55,0 -1,0.45 -1,1s0.45,1 1,1zM20,13h2c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1h-2c-0.55,0 -1,0.45 -1,1s0.45,1 1,1zM11,2v2c0,0.55 0.45,1 1,1s1,-0.45 1,-1L13,2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1zM11,20v2c0,0.55 0.45,1 1,1s1,-0.45 1,-1v-2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1zM5.99,4.58c-0.39,-0.39 -1.03,-0.39 -1.41,0s-0.39,1.03 0,1.41l1.06,1.06c0.39,0.39 1.03,0.39 1.41,0s0.39,-1.03 0,-1.41L5.99,4.58zM18.36,16.95c-0.39,-0.39 -1.03,-0.39 -1.41,0s-0.39,1.03 0,1.41l1.06,1.06c0.39,0.39 1.03,0.39 1.41,0s0.39,-1.03 0,-1.41l-1.06,-1.06zM19.42,5.99c0.39,-0.39 0.39,-1.03 0,-1.41s-1.03,-0.39 -1.41,0l-1.06,1.06c-0.39,0.39 -0.39,1.03 0,1.41s1.03,0.39 1.41,0l1.06,-1.06zM7.05,18.36c0.39,-0.39 0.39,-1.03 0,-1.41s-1.03,-0.39 -1.41,0l-1.06,1.06c-0.39,0.39 -0.39,1.03 0,1.41s1.03,0.39 1.41,0l1.06,-1.06z" />
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="#FFFFFF">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM12,20V4c4.42,0 8,3.58 8,8s-3.58,8 -8,8z" />
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners android:radius="12dp" />
|
||||||
|
<solid android:color="#FFFFFF" /> <!-- Default color, replaced by backgroundTint -->
|
||||||
|
</shape>
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="2dp" >
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true" >
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="100px"
|
||||||
|
android:layout_height="100px"
|
||||||
|
android:layout_marginLeft="5px"
|
||||||
|
android:layout_marginRight="20px"
|
||||||
|
android:layout_marginTop="5px" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/name"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="50px"
|
||||||
|
android:maxEms="14"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:singleLine="true" />
|
||||||
|
</LinearLayout>
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/checked"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:focusable="false"
|
||||||
|
android:clickable="false" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
@ -0,0 +1,279 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?android:attr/windowBackground">
|
||||||
|
|
||||||
|
<!-- Custom Header/Toolbar -->
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?android:attr/actionBarSize"
|
||||||
|
android:background="#1A1A1A"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/header_icon"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:src="@mipmap/ic_launcher" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_toEndOf="@id/header_icon"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<!-- Triple Toggle Theme ImageButton -->
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/theme_toggle"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_theme_system"
|
||||||
|
android:contentDescription="Toggle Theme"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:scaleType="fitCenter" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<!-- HR above Watchdog -->
|
||||||
|
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#444444" android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- Watchdog Control Section -->
|
||||||
|
<Button
|
||||||
|
android:id="@+id/watchdog_control"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="90dp"
|
||||||
|
android:text="Enable Master Watchdog"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:background="@drawable/rounded_button"
|
||||||
|
android:backgroundTint="#1976D2"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:textAllCaps="false"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Protects Termux from Doze mode and keeps Wi-Fi active."
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- HR below Watchdog -->
|
||||||
|
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#444444" android:layout_marginBottom="20dp"/>
|
||||||
|
|
||||||
|
<!-- VPN Control Section -->
|
||||||
|
<Button
|
||||||
|
android:id="@+id/control"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="90dp"
|
||||||
|
android:text="@string/control_enable"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:background="@drawable/rounded_button"
|
||||||
|
android:backgroundTint="#2E7D32"
|
||||||
|
android:textAllCaps="false"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/vpn_description"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="12dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/apps"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/apps"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:background="@drawable/rounded_button"
|
||||||
|
android:backgroundTint="#444444"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textAllCaps="false"/>
|
||||||
|
|
||||||
|
<!-- Config Section (Below VPN Button) -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/config_label"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="▶ Configuration"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:background="?attr/sectionHeaderBackground"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:layout_marginTop="20dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/config_layout"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:background="?attr/sectionBackground">
|
||||||
|
|
||||||
|
<!-- Primary Fields -->
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_addr" android:textColor="?android:attr/textColorSecondary" android:textSize="12sp"/>
|
||||||
|
<EditText android:id="@+id/socks_addr" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:textColor="?android:attr/textColorPrimary"/>
|
||||||
|
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_port" android:textColor="?android:attr/textColorSecondary" android:textSize="12sp"/>
|
||||||
|
<EditText android:id="@+id/socks_port" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:inputType="number" android:textColor="?android:attr/textColorPrimary"/>
|
||||||
|
|
||||||
|
<!-- Advanced Sub-section -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/adv_config_label"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="▶ Advanced Settings"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/advanced_config"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_udp_addr" android:textColor="?android:attr/textColorSecondary" android:textSize="11sp"/>
|
||||||
|
<EditText android:id="@+id/socks_udp_addr" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:textColor="?android:attr/textColorPrimary"/>
|
||||||
|
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_user" android:textColor="?android:attr/textColorSecondary" android:textSize="11sp"/>
|
||||||
|
<EditText android:id="@+id/socks_user" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:textColor="?android:attr/textColorPrimary"/>
|
||||||
|
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_pass" android:textColor="?android:attr/textColorSecondary" android:textSize="11sp"/>
|
||||||
|
<EditText android:id="@+id/socks_pass" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:inputType="textPassword" android:textColor="?android:attr/textColorPrimary"/>
|
||||||
|
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/dns_ipv4" android:textColor="?android:attr/textColorSecondary" android:textSize="11sp"/>
|
||||||
|
<EditText android:id="@+id/dns_ipv4" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:textColor="?android:attr/textColorPrimary"/>
|
||||||
|
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/dns_ipv6" android:textColor="?android:attr/textColorSecondary" android:textSize="11sp"/>
|
||||||
|
<EditText android:id="@+id/dns_ipv6" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:textColor="?android:attr/textColorPrimary"/>
|
||||||
|
|
||||||
|
<CheckBox android:id="@+id/udp_in_tcp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/udp_in_tcp" android:textColor="?android:attr/textColorSecondary"/>
|
||||||
|
<CheckBox android:id="@+id/ipv4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/ipv4" android:textColor="?android:attr/textColorSecondary"/>
|
||||||
|
<CheckBox android:id="@+id/ipv6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/ipv6" android:textColor="?android:attr/textColorSecondary"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp">
|
||||||
|
<CheckBox android:id="@+id/remote_dns" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/remote_dns" android:textColor="?android:attr/textColorSecondary"/>
|
||||||
|
<CheckBox android:id="@+id/global" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/global" android:textColor="?android:attr/textColorSecondary"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Button android:id="@+id/save" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/save" android:layout_marginTop="8dp" android:background="@drawable/rounded_button" android:backgroundTint="#555555" android:textColor="#FFFFFF" android:textAllCaps="false"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#444444" android:layout_marginVertical="20dp"/>
|
||||||
|
|
||||||
|
<!-- Log Section (Collapsible) -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/log_label"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="▶ Connection Log"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:background="?attr/sectionHeaderBackground"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/connection_log"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="250dp"
|
||||||
|
android:background="#000000"
|
||||||
|
android:textColor="#00FF00"
|
||||||
|
android:fontFamily="monospace"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
android:text="System ready...\n"
|
||||||
|
android:textSize="11sp"/>
|
||||||
|
|
||||||
|
<!-- Log Actions Bar -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/log_actions"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_marginTop="4dp">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_clear_log"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Reset Log"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:backgroundTint="#D32F2F"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:layout_marginEnd="4dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_copy_log"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Copy All"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:backgroundTint="#388E3C"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:layout_marginStart="4dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Version Footer -->
|
||||||
|
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#444444" android:layout_marginTop="32dp" android:layout_marginBottom="8dp"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/version_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="v0.1.17alpha"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textSize="10sp"
|
||||||
|
android:paddingBottom="16dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
</LinearLayout>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 870 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<style name="Theme.IIABController" parent="Theme.AppCompat.NoActionBar">
|
||||||
|
<item name="colorPrimary">#000000</item>
|
||||||
|
<item name="colorPrimaryDark">#000000</item>
|
||||||
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
|
|
||||||
|
<item name="android:windowBackground">@color/background_dark</item>
|
||||||
|
<item name="android:textColorPrimary">@color/white</item>
|
||||||
|
|
||||||
|
<item name="sectionBackground">@color/section_body_bg_dark</item>
|
||||||
|
<item name="sectionHeaderBackground">@color/black</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">SocksTun</string>
|
||||||
|
<string name="socks_addr">Адрес Socks:</string>
|
||||||
|
<string name="socks_udp_addr">UDP-aдрес Socks:</string>
|
||||||
|
<string name="socks_port">Порт Socks:</string>
|
||||||
|
<string name="socks_user">Имя пользователя Socks:</string>
|
||||||
|
<string name="socks_pass">Пароль Socks:</string>
|
||||||
|
<string name="dns_ipv4">DNS IPv4:</string>
|
||||||
|
<string name="dns_ipv6">DNS IPv6:</string>
|
||||||
|
<string name="udp_in_tcp">UDP через TCP</string>
|
||||||
|
<string name="remote_dns">Удалённый DNS</string>
|
||||||
|
<string name="ipv4">IPv4</string>
|
||||||
|
<string name="ipv6">IPv6</string>
|
||||||
|
<string name="global">Глобально</string>
|
||||||
|
<string name="apps">Приложения</string>
|
||||||
|
<string name="save">Сохранить</string>
|
||||||
|
<string name="control_enable">Включить</string>
|
||||||
|
<string name="control_disable">Отключить</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<attr name="sectionBackground" format="color" />
|
||||||
|
<attr name="sectionHeaderBackground" format="color" />
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="colorPrimary">#1A1A1A</color>
|
||||||
|
<color name="colorPrimaryDark">#000000</color>
|
||||||
|
<color name="colorAccent">#2E7D32</color>
|
||||||
|
<color name="white">#FFFFFF</color>
|
||||||
|
<color name="black">#000000</color>
|
||||||
|
|
||||||
|
<color name="background_dark">#121212</color>
|
||||||
|
|
||||||
|
<!-- Status Colors (Fixed for readability) -->
|
||||||
|
<color name="btn_watchdog_on">#D32F2F</color> <!-- Material Red -->
|
||||||
|
<color name="btn_watchdog_off">#1976D2</color> <!-- Material Blue -->
|
||||||
|
<color name="btn_vpn_on">#C62828</color> <!-- Dark Red -->
|
||||||
|
<color name="btn_vpn_off">#2E7D32</color> <!-- Material Green -->
|
||||||
|
|
||||||
|
<!-- Secciones -->
|
||||||
|
<color name="section_header_bg">#333333</color>
|
||||||
|
<color name="section_body_bg_light">#F5F5F5</color>
|
||||||
|
<color name="section_body_bg_dark">#1A1A1A</color>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">IIAB-oA Controller</string>
|
||||||
|
<string name="socks_addr">Socks Address:</string>
|
||||||
|
<string name="socks_udp_addr">Socks UDP Address:</string>
|
||||||
|
<string name="socks_port">Socks Port:</string>
|
||||||
|
<string name="socks_user">Socks Username:</string>
|
||||||
|
<string name="socks_pass">Socks Password:</string>
|
||||||
|
<string name="dns_ipv4">DNS IPv4:</string>
|
||||||
|
<string name="dns_ipv6">DNS IPv6:</string>
|
||||||
|
<string name="udp_in_tcp">UDP relay over TCP</string>
|
||||||
|
<string name="remote_dns">Remote DNS</string>
|
||||||
|
<string name="ipv4">IPv4</string>
|
||||||
|
<string name="ipv6">IPv6</string>
|
||||||
|
<string name="global">Global</string>
|
||||||
|
<string name="apps">Apps</string>
|
||||||
|
<string name="save">Save</string>
|
||||||
|
<string name="control_enable">Enable Safe Pocket Web</string>
|
||||||
|
<string name="control_disable">Disable Safe Pocket Web</string>
|
||||||
|
<string name="vpn_description">Enable friendly URLs. Lock out the threats.</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<style name="Theme.IIABController" parent="Theme.AppCompat.Light.NoActionBar">
|
||||||
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
|
|
||||||
|
<item name="android:windowBackground">@color/white</item>
|
||||||
|
<item name="android:textColorPrimary">@color/black</item>
|
||||||
|
|
||||||
|
<item name="sectionBackground">@color/section_body_bg_light</item>
|
||||||
|
<item name="sectionHeaderBackground">@color/section_header_bg</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
/**
|
||||||
|
* The buildscript block is where you configure the repositories and
|
||||||
|
* dependencies for Gradle itself--meaning, you should not include dependencies
|
||||||
|
* for your modules here. For example, this block includes the Android plugin for
|
||||||
|
* Gradle as a dependency because it provides the additional instructions Gradle
|
||||||
|
* needs to build Android app modules.
|
||||||
|
*/
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The repositories block configures the repositories Gradle uses to
|
||||||
|
* search or download the dependencies. Gradle pre-configures support for remote
|
||||||
|
* repositories such as JCenter, Maven Central, and Ivy. You can also use local
|
||||||
|
* repositories or define your own remote repositories. The code below defines
|
||||||
|
* JCenter as the repository Gradle should use to look for its dependencies.
|
||||||
|
*/
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The dependencies block configures the dependencies Gradle needs to use
|
||||||
|
* to build your project. The following line adds Android plugin for Gradle
|
||||||
|
* version 2.3.1 as a classpath dependency.
|
||||||
|
*/
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:8.4.1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The allprojects block is where you configure the repositories and
|
||||||
|
* dependencies used by all modules in your project, such as third-party plugins
|
||||||
|
* or libraries. Dependencies that are not required by all the modules in the
|
||||||
|
* project should be configured in module-level build.gradle files. For new
|
||||||
|
* projects, Android Studio configures JCenter as the default repository, but it
|
||||||
|
* does not configure any dependencies.
|
||||||
|
*/
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
A simple and lightweight VPN over socks5 proxy for Android. It is based on a high-performance and low-overhead tun2socks.
|
||||||
|
|
||||||
|
<h2>Features</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Redirect TCP connections.</li>
|
||||||
|
<li>Redirect UDP packets. (Fullcone NAT, UDP in UDP/TCP)</li>
|
||||||
|
<li>Simple username/password authentication.</li>
|
||||||
|
<li>Specifying DNS addresses.</li>
|
||||||
|
<li>IPv4/IPv6 dual stack.</li>
|
||||||
|
<li>Global/per-App modes.</li>
|
||||||
|
</ul>
|
||||||
|
After Width: | Height: | Size: 443 KiB |
|
|
@ -0,0 +1 @@
|
||||||
|
A simple and lightweight VPN over socks5 proxy (tun2socks)
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
Простое и лёгкое VPN-решение поверх Socks5 прокси для Android. Основано на высокопроизводительном и малозатратном tun2socks.
|
||||||
|
|
||||||
|
<h2>Особенности</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Перенаправление TCP-соединений.</li>
|
||||||
|
<li>Перенаправление UDP-пакетов. (Fullcone NAT, UDP внутри UDP/TCP)</li>
|
||||||
|
<li>Простая аутентификация по имени пользователя и паролю.</li>
|
||||||
|
<li>Указание адресов DNS.</li>
|
||||||
|
<li>Поддержка двойного стека IPv4/IPv6.</li>
|
||||||
|
<li>Глобальный режим и режим для отдельных приложений.</li>
|
||||||
|
</ul>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
Простое и лёгкое VPN поверх Socks5 прокси (tun2socks)
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
android.enableJetifier=true
|
||||||
|
android.useAndroidX=true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionSha256Sum=a4b4158601f8636cdeeab09bd76afb640030bb5b144aafe261a5e8af027dc612
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
|
@ -0,0 +1,188 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2015 the original author or authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=$(save "$@")
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||||
|
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
## This file must *NOT* be checked into Version Control Systems,
|
||||||
|
# as it contains information specific to your local configuration.
|
||||||
|
#
|
||||||
|
# Location of the SDK. This is only used by Gradle.
|
||||||
|
# For customization when using a Version Control System, please read the
|
||||||
|
# header note.
|
||||||
|
#Tue Mar 03 10:51:56 CST 2026
|
||||||
|
sdk.dir=/home/ark/Android/Sdk
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
include ':app'
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
storeFile=debug.keystore
|
||||||
|
keyAlias=androiddebugkey
|
||||||
|
storePassword=android
|
||||||
|
keyPassword=android
|
||||||