Skip to content

Commit c6361f2

Browse files
madeyeclaude
andauthored
Modernize build config and add VPN service with tun2socks support (#33)
* Add VPN mode support without requiring root permissions This implements PR #32 features: - Add ProxyDroidVpnService extending Android's VpnService API - Add native tun2socks library for TUN to SOCKS5 conversion - Add LocalProxyServer for HTTP-to-SOCKS protocol conversion - Add Tun2SocksHelper JNI wrapper for native library - Update build.gradle to SDK 33, Gradle 8.x, NDK 25.1 - Add VPN mode toggle in preferences (enabled by default) - Update AndroidManifest with VPN service declaration and permissions https://claude.ai/code/session_01XDv7Fhpou2mSbpsV56cWxL * Add GitHub Actions CI workflow and unit tests - Add android-build.yml workflow for CI/CD with: - JDK 17 and Android SDK setup - Gradle build, unit tests, and lint - APK and test results artifact upload - Add LocalHttpProxyTest with comprehensive unit tests for: - SOCKS5 handshake (auth and no-auth) - SOCKS5 connect requests (IPv4, domain, IPv6) - HTTP CONNECT proxy requests - Protocol parsing and encoding https://claude.ai/code/session_01XDv7Fhpou2mSbpsV56cWxL * Add local HTTP proxy tests with Android emulator integration - Add emulator-test job to GitHub Actions workflow that: - Starts an HTTP CONNECT proxy server on the host - Installs the app on Android emulator - Configures proxy settings via adb - Verifies VPN service declaration and native libraries - Tests app startup and configuration handling - Add test_http_proxy.py: Python HTTP CONNECT proxy for testing - Supports authentication - Logs all CONNECT requests - Relays data between client and target - Add run_integration_tests.sh: Integration test script - Automates emulator testing workflow - Configures preferences via adb - Runs connectivity verification tests - Update app/build.gradle with additional androidTest dependencies https://claude.ai/code/session_01XDv7Fhpou2mSbpsV56cWxL * Remove unused imports and constants from unit tests Clean up test file to remove unused imports (ServerSocket, Socket, InputStream, OutputStream, etc.) and unused port constants that were left over from the previous socket-based test implementation. https://claude.ai/code/session_01XDv7Fhpou2mSbpsV56cWxL * Fix CI lint job by adding NDK/CMake installation The lint job was failing because it couldn't compile native code without NDK and CMake installed. Also removed unnecessary dummy google-services.json from lint job since Firebase is not used. https://claude.ai/code/session_01XDv7Fhpou2mSbpsV56cWxL * Fix build error: remove API 34+ foregroundServiceType The 'specialUse' foregroundServiceType and FOREGROUND_SERVICE_SPECIAL_USE permission were added in Android 14 (API 34), but the app targets API 33. VPN services don't need a specific foregroundServiceType since VpnService has special handling built-in. https://claude.ai/code/session_01XDv7Fhpou2mSbpsV56cWxL * Make gradlew executable https://claude.ai/code/session_01XDv7Fhpou2mSbpsV56cWxL * Fix JUnit exclusion to not affect test configurations The previous configuration excluded JUnit from all configurations which broke unit tests. Now JUnit is only excluded from implementation scope to prevent json-simple's transitive JUnit dependency from being included in the APK, while still allowing testImplementation to use JUnit. https://claude.ai/code/session_01XDv7Fhpou2mSbpsV56cWxL * Remove Firebase/AdMob dependencies and fix R.id switch statements - Remove Firebase Analytics from ProxyDroidApplication - Remove AdMob (AdView, AdSize, AdRequest) from ProxyDroid - Convert switch statement with R.id.* to if-else in BypassListActivity (R.id fields are not constant expressions with Android Gradle Plugin) - Remove unused getLayout method, ViewParent and LinearLayout imports https://claude.ai/code/session_01XDv7Fhpou2mSbpsV56cWxL * Remove remaining Firebase Analytics references Remove firebaseAnalytics calls from: - AppManager.java - FileChooser.java - ProxyDroidService.java (onDestroy and onStart) https://claude.ai/code/session_01XDv7Fhpou2mSbpsV56cWxL * Fix configurations syntax in build.gradle Use configurations.implementation instead of configurations { implementation { } } to properly exclude JUnit from implementation configuration. https://claude.ai/code/session_01XDv7Fhpou2mSbpsV56cWxL * Fix JUnit exclusion: exclude only from json-simple dependency The previous configuration excluded JUnit from all implementation configurations which was also blocking it from test configurations. Now JUnit is only excluded from the json-simple dependency which incorrectly pulls it in as a compile dependency. https://claude.ai/code/session_01XDv7Fhpou2mSbpsV56cWxL * Fix shell quoting in emulator test script Use heredoc (cat << 'PREFS_EOF') instead of variable assignment to avoid shell parsing issues with XML special characters. https://claude.ai/code/session_01XDv7Fhpou2mSbpsV56cWxL * Fix emulator test: create prefs.xml in separate step The android-emulator-runner action executes script lines individually, so heredocs don't work within the script block. Move the prefs.xml creation to a separate step that runs before the emulator. https://claude.ai/code/session_01XDv7Fhpou2mSbpsV56cWxL * Add Python cache to .gitignore https://claude.ai/code/session_01XDv7Fhpou2mSbpsV56cWxL * Move emulator test script to separate file Extract inline emulator test script from workflow YAML into scripts/run_emulator_tests.sh for easier maintenance and testing. https://claude.ai/code/session_01XDv7Fhpou2mSbpsV56cWxL * Enable emulator snapshots in CI Remove -no-snapshot-save flag to allow emulator snapshots, which can speed up boot times on subsequent runs. https://claude.ai/code/session_01XDv7Fhpou2mSbpsV56cWxL * Fix emulator test script execution Use 'bash scripts/run_emulator_tests.sh' instead of './scripts/...' to ensure the script is found and executed correctly. https://claude.ai/code/session_01XDv7Fhpou2mSbpsV56cWxL * Fix line endings in emulator test script Convert CRLF to LF line endings to fix bash syntax error. https://claude.ai/code/session_01XDv7Fhpou2mSbpsV56cWxL --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3de979e commit c6361f2

28 files changed

Lines changed: 2844 additions & 124 deletions
Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
name: Android Build
2+
3+
on:
4+
push:
5+
branches: [ master, main, 'claude/**' ]
6+
pull_request:
7+
branches: [ master, main ]
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
17+
- name: Set up JDK 17
18+
uses: actions/setup-java@v4
19+
with:
20+
java-version: '17'
21+
distribution: 'temurin'
22+
23+
- name: Setup Android SDK
24+
uses: android-actions/setup-android@v3
25+
26+
- name: Install NDK and CMake
27+
run: |
28+
sdkmanager --install "ndk;25.1.8937393" "cmake;3.22.1"
29+
echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/25.1.8937393" >> $GITHUB_ENV
30+
31+
- name: Cache Gradle packages
32+
uses: actions/cache@v4
33+
with:
34+
path: |
35+
~/.gradle/caches
36+
~/.gradle/wrapper
37+
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
38+
restore-keys: |
39+
${{ runner.os }}-gradle-
40+
41+
- name: Grant execute permission for gradlew
42+
run: chmod +x gradlew
43+
44+
- name: Create local.properties
45+
run: |
46+
echo "sdk.dir=$ANDROID_HOME" > local.properties
47+
echo "ndk.dir=$ANDROID_NDK_HOME" >> local.properties
48+
49+
- name: Create dummy google-services.json
50+
run: |
51+
mkdir -p app
52+
cat > app/google-services.json << 'EOF'
53+
{
54+
"project_info": {
55+
"project_number": "000000000000",
56+
"project_id": "dummy-project",
57+
"storage_bucket": "dummy-project.appspot.com"
58+
},
59+
"client": [
60+
{
61+
"client_info": {
62+
"mobilesdk_app_id": "1:000000000000:android:0000000000000000",
63+
"android_client_info": {
64+
"package_name": "org.proxydroid"
65+
}
66+
},
67+
"oauth_client": [],
68+
"api_key": [
69+
{
70+
"current_key": "dummy-api-key"
71+
}
72+
],
73+
"services": {
74+
"appinvite_service": {
75+
"other_platform_oauth_client": []
76+
}
77+
}
78+
}
79+
],
80+
"configuration_version": "1"
81+
}
82+
EOF
83+
84+
- name: Build Debug APK
85+
run: ./gradlew assembleDebug --stacktrace
86+
87+
- name: Upload Debug APK
88+
uses: actions/upload-artifact@v4
89+
with:
90+
name: app-debug
91+
path: app/build/outputs/apk/debug/app-debug.apk
92+
retention-days: 7
93+
94+
lint:
95+
runs-on: ubuntu-latest
96+
97+
steps:
98+
- name: Checkout code
99+
uses: actions/checkout@v4
100+
101+
- name: Set up JDK 17
102+
uses: actions/setup-java@v4
103+
with:
104+
java-version: '17'
105+
distribution: 'temurin'
106+
107+
- name: Setup Android SDK
108+
uses: android-actions/setup-android@v3
109+
110+
- name: Install NDK and CMake
111+
run: |
112+
sdkmanager --install "ndk;25.1.8937393" "cmake;3.22.1"
113+
echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/25.1.8937393" >> $GITHUB_ENV
114+
115+
- name: Cache Gradle packages
116+
uses: actions/cache@v4
117+
with:
118+
path: |
119+
~/.gradle/caches
120+
~/.gradle/wrapper
121+
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
122+
restore-keys: |
123+
${{ runner.os }}-gradle-
124+
125+
- name: Grant execute permission for gradlew
126+
run: chmod +x gradlew
127+
128+
- name: Create local.properties
129+
run: |
130+
echo "sdk.dir=$ANDROID_HOME" > local.properties
131+
echo "ndk.dir=$ANDROID_NDK_HOME" >> local.properties
132+
133+
- name: Run Lint
134+
run: ./gradlew lint --stacktrace
135+
continue-on-error: true
136+
137+
- name: Upload Lint Results
138+
uses: actions/upload-artifact@v4
139+
if: always()
140+
with:
141+
name: lint-results
142+
path: app/build/reports/lint-results*.html
143+
retention-days: 7
144+
145+
test:
146+
runs-on: ubuntu-latest
147+
148+
steps:
149+
- name: Checkout code
150+
uses: actions/checkout@v4
151+
152+
- name: Set up JDK 17
153+
uses: actions/setup-java@v4
154+
with:
155+
java-version: '17'
156+
distribution: 'temurin'
157+
158+
- name: Setup Android SDK
159+
uses: android-actions/setup-android@v3
160+
161+
- name: Install NDK and CMake
162+
run: |
163+
sdkmanager --install "ndk;25.1.8937393" "cmake;3.22.1"
164+
echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/25.1.8937393" >> $GITHUB_ENV
165+
166+
- name: Cache Gradle packages
167+
uses: actions/cache@v4
168+
with:
169+
path: |
170+
~/.gradle/caches
171+
~/.gradle/wrapper
172+
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
173+
restore-keys: |
174+
${{ runner.os }}-gradle-
175+
176+
- name: Grant execute permission for gradlew
177+
run: chmod +x gradlew
178+
179+
- name: Create local.properties
180+
run: |
181+
echo "sdk.dir=$ANDROID_HOME" > local.properties
182+
echo "ndk.dir=$ANDROID_NDK_HOME" >> local.properties
183+
184+
- name: Create dummy google-services.json
185+
run: |
186+
mkdir -p app
187+
cat > app/google-services.json << 'EOF'
188+
{
189+
"project_info": {
190+
"project_number": "000000000000",
191+
"project_id": "dummy-project",
192+
"storage_bucket": "dummy-project.appspot.com"
193+
},
194+
"client": [
195+
{
196+
"client_info": {
197+
"mobilesdk_app_id": "1:000000000000:android:0000000000000000",
198+
"android_client_info": {
199+
"package_name": "org.proxydroid"
200+
}
201+
},
202+
"oauth_client": [],
203+
"api_key": [
204+
{
205+
"current_key": "dummy-api-key"
206+
}
207+
],
208+
"services": {
209+
"appinvite_service": {
210+
"other_platform_oauth_client": []
211+
}
212+
}
213+
}
214+
],
215+
"configuration_version": "1"
216+
}
217+
EOF
218+
219+
- name: Run Unit Tests
220+
run: ./gradlew testDebugUnitTest --stacktrace
221+
222+
- name: Upload Test Results
223+
uses: actions/upload-artifact@v4
224+
if: always()
225+
with:
226+
name: test-results
227+
path: |
228+
app/build/reports/tests/
229+
app/build/test-results/
230+
retention-days: 7
231+
232+
emulator-test:
233+
runs-on: ubuntu-latest
234+
needs: build
235+
236+
steps:
237+
- name: Checkout code
238+
uses: actions/checkout@v4
239+
240+
- name: Set up JDK 17
241+
uses: actions/setup-java@v4
242+
with:
243+
java-version: '17'
244+
distribution: 'temurin'
245+
246+
- name: Setup Android SDK
247+
uses: android-actions/setup-android@v3
248+
249+
- name: Install NDK and CMake
250+
run: |
251+
sdkmanager --install "ndk;25.1.8937393" "cmake;3.22.1"
252+
echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/25.1.8937393" >> $GITHUB_ENV
253+
254+
- name: Cache Gradle packages
255+
uses: actions/cache@v4
256+
with:
257+
path: |
258+
~/.gradle/caches
259+
~/.gradle/wrapper
260+
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
261+
restore-keys: |
262+
${{ runner.os }}-gradle-
263+
264+
- name: Grant execute permission for gradlew
265+
run: chmod +x gradlew
266+
267+
- name: Create local.properties
268+
run: |
269+
echo "sdk.dir=$ANDROID_HOME" > local.properties
270+
echo "ndk.dir=$ANDROID_NDK_HOME" >> local.properties
271+
272+
- name: Create dummy google-services.json
273+
run: |
274+
mkdir -p app
275+
cat > app/google-services.json << 'EOF'
276+
{
277+
"project_info": {
278+
"project_number": "000000000000",
279+
"project_id": "dummy-project",
280+
"storage_bucket": "dummy-project.appspot.com"
281+
},
282+
"client": [
283+
{
284+
"client_info": {
285+
"mobilesdk_app_id": "1:000000000000:android:0000000000000000",
286+
"android_client_info": {
287+
"package_name": "org.proxydroid"
288+
}
289+
},
290+
"oauth_client": [],
291+
"api_key": [
292+
{
293+
"current_key": "dummy-api-key"
294+
}
295+
],
296+
"services": {
297+
"appinvite_service": {
298+
"other_platform_oauth_client": []
299+
}
300+
}
301+
}
302+
],
303+
"configuration_version": "1"
304+
}
305+
EOF
306+
307+
- name: Enable KVM
308+
run: |
309+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
310+
sudo udevadm control --reload-rules
311+
sudo udevadm trigger --name-match=kvm
312+
313+
- name: Build Debug APK
314+
run: ./gradlew assembleDebug --stacktrace
315+
316+
- name: Start HTTP Proxy Server
317+
run: |
318+
chmod +x scripts/test_http_proxy.py
319+
python3 scripts/test_http_proxy.py --port 8888 &
320+
echo "PROXY_PID=$!" >> $GITHUB_ENV
321+
sleep 2
322+
echo "HTTP proxy started on port 8888"
323+
324+
- name: Create proxy preferences file
325+
run: |
326+
cat > /tmp/prefs.xml << 'EOF'
327+
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
328+
<map>
329+
<string name="host">10.0.2.2</string>
330+
<string name="port">8888</string>
331+
<string name="proxyType">http</string>
332+
<boolean name="isVpnMode" value="true" />
333+
<boolean name="isAutoConnect" value="false" />
334+
<boolean name="isAuth" value="false" />
335+
<string name="user"></string>
336+
<string name="password"></string>
337+
<boolean name="isRunning" value="false" />
338+
<boolean name="isBypassApps" value="false" />
339+
<string name="bypassAddrs"></string>
340+
<boolean name="isGlobalProxy" value="true" />
341+
</map>
342+
EOF
343+
344+
- name: Run Emulator Tests
345+
uses: reactivecircus/android-emulator-runner@v2
346+
with:
347+
api-level: 29
348+
arch: x86_64
349+
profile: Nexus 6
350+
force-avd-creation: false
351+
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
352+
disable-animations: true
353+
script: bash scripts/run_emulator_tests.sh
354+
355+
- name: Stop HTTP Proxy
356+
if: always()
357+
run: |
358+
if [ -n "$PROXY_PID" ]; then
359+
kill $PROXY_PID 2>/dev/null || true
360+
fi
361+
362+
- name: Upload Test Logs
363+
uses: actions/upload-artifact@v4
364+
if: always()
365+
with:
366+
name: emulator-test-logs
367+
path: |
368+
app/build/outputs/
369+
retention-days: 7

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ classes/
1414
.gradle
1515
build/
1616
app/release
17+
__pycache__/
18+
*.pyc

0 commit comments

Comments
 (0)