Skip to content

Commit d2b881d

Browse files
committed
Initial NexusBridge Android app and server
Add a complete NexusBridge prototype: Android app source, resources and build files, a simple Python relay server, CI workflow, and documentation. Android changes include app manifest, Gradle configuration, proguard rules, activities/fragments (Pairing, Home, MainActivity), BridgeService (foreground WebSocket bridge), SmsRepository, SmsReceiver, TLS helper and UI layouts/drawables. Also add GitHub Actions workflow to build the APK, README with project overview and usage, index.html web client, server.py and requirements.txt for the relay server. This commit bootstraps the project for local development, building and deployment.
0 parents  commit d2b881d

53 files changed

Lines changed: 5118 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
name: Build Android APK
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
paths:
7+
- 'android/**'
8+
- '.github/workflows/build-android.yml'
9+
pull_request:
10+
branches: [ main ]
11+
paths:
12+
- 'android/**'
13+
workflow_dispatch:
14+
15+
jobs:
16+
build:
17+
runs-on: self-hosted
18+
19+
steps:
20+
- name: Checkout
21+
uses: actions/checkout@v4
22+
23+
# ── Java 21 ─────────────────────────────────────────────────────────────
24+
- name: Set up Java 21
25+
uses: actions/setup-java@v4
26+
with:
27+
distribution: temurin
28+
java-version: '21'
29+
30+
# ── Android SDK ──────────────────────────────────────────────────────────
31+
- name: Install Android SDK
32+
shell: powershell
33+
working-directory: android
34+
run: |
35+
$SDK_DIR = "$env:USERPROFILE\.android\sdk"
36+
$SDKMANAGER = "$SDK_DIR\cmdline-tools\latest\bin\sdkmanager.bat"
37+
38+
if (-not (Test-Path "$SDK_DIR\platform-tools\adb.exe")) {
39+
Write-Host "Downloading Android cmdline-tools..."
40+
$ZIP = "$env:TEMP\cmdline-tools.zip"
41+
$EXTRACT = "$env:TEMP\cmdline-tools-extract"
42+
43+
& curl.exe -fsSL -o $ZIP "https://dl.google.com/android/repository/commandlinetools-win-12266719_latest.zip"
44+
45+
New-Item -ItemType Directory -Force -Path $EXTRACT | Out-Null
46+
Expand-Archive -LiteralPath $ZIP -DestinationPath $EXTRACT -Force
47+
New-Item -ItemType Directory -Force -Path "$SDK_DIR\cmdline-tools\latest" | Out-Null
48+
Copy-Item "$EXTRACT\cmdline-tools\*" "$SDK_DIR\cmdline-tools\latest\" -Recurse -Force
49+
Remove-Item $ZIP, $EXTRACT -Recurse -Force -ErrorAction SilentlyContinue
50+
} else {
51+
Write-Host "Android SDK already present at $SDK_DIR"
52+
}
53+
54+
# Write license hashes directly — most reliable method for CI
55+
$LICENSES_DIR = "$SDK_DIR\licenses"
56+
New-Item -ItemType Directory -Force -Path $LICENSES_DIR | Out-Null
57+
[System.IO.File]::WriteAllText(
58+
"$LICENSES_DIR\android-sdk-license",
59+
"`n8933bad161af4178b1185d1a37fbf41ea5269c55`nd56f5187479451eabf01fb78af6dfcb131a6481e`n24333f8a63b6825ea9c5514f83c2829b004d1fee"
60+
)
61+
[System.IO.File]::WriteAllText(
62+
"$LICENSES_DIR\android-sdk-preview-license",
63+
"`n84831b9409646a918e30573bab4c9c91346d8abd"
64+
)
65+
[System.IO.File]::WriteAllText(
66+
"$LICENSES_DIR\android-sdk-arm-dbt-license",
67+
"`n859f317696f67ef3d7f30a50a5560e7834b43903"
68+
)
69+
70+
$env:ANDROID_HOME = $SDK_DIR
71+
& $SDKMANAGER "platform-tools" "platforms;android-34" "build-tools;35.0.0"
72+
73+
$sdk_path = $SDK_DIR -replace '\\', '/'
74+
"sdk.dir=$sdk_path" | Set-Content -Path "local.properties" -Encoding UTF8
75+
"ANDROID_HOME=$SDK_DIR" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
76+
77+
# ── Gradle cache ─────────────────────────────────────────────────────────
78+
- name: Cache Gradle packages
79+
uses: actions/cache@v4
80+
with:
81+
path: |
82+
~\.gradle\caches
83+
~\.gradle\wrapper
84+
key: gradle-${{ runner.os }}-${{ hashFiles('android/**/*.gradle', 'android/gradle/wrapper/gradle-wrapper.properties') }}
85+
restore-keys: |
86+
gradle-${{ runner.os }}-
87+
88+
# ── Build ────────────────────────────────────────────────────────────────
89+
- name: Build debug APK
90+
shell: powershell
91+
working-directory: android
92+
run: .\gradlew.bat assembleDebug --no-daemon --warning-mode all
93+
94+
# ── Upload artifact ───────────────────────────────────────────────────────
95+
- name: Upload APK
96+
uses: actions/upload-artifact@v4
97+
with:
98+
name: app-debug-${{ github.sha }}
99+
path: android/app/build/outputs/apk/debug/app-debug.apk
100+
retention-days: 30

README.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# NexusBridge ![GitHub Downloads (latest release)](https://img.shields.io/github/downloads/JMTDI/sms-bridge/latest/total)
2+
3+
A complete SMS Bridge system that connects an Android phone to a web-based messaging UI via a WebSocket relay server.
4+
5+
## Get Started
6+
7+
👉 **Go to [https://sms-bridge-jmtdi.devpush.app/](https://sms-bridge-jmtdi.devpush.app/)**
8+
9+
The builder will walk you through three steps:
10+
11+
1. **Deploy your server** — click the "Deploy to Railway" button to spin up a free server in one click
12+
2. **Enter your server URL** — paste the Railway URL you get after deploying
13+
3. **Build your app** — the builder generates and downloads a zip containing:
14+
- `nexusbridge.apk` — ready-to-install Android app
15+
- `index.html` — web messaging UI, pre-configured for your server
16+
17+
No coding required.
18+
19+
---
20+
21+
## Self-Hosting the Builder
22+
23+
If you want to run the builder yourself instead of using the hosted version:
24+
25+
```bash
26+
git clone https://github.com/JMTDI/sms-bridge
27+
cd sms-bridge
28+
pip install -r requirements.txt
29+
python builder.py
30+
```
31+
32+
Open **http://localhost:5000** in your browser.
33+
34+
## Architecture
35+
36+
```
37+
Android App (phone) ──WebSocket──▶ server.py ──WebSocket──▶ index.html (browser)
38+
39+
REST API
40+
/new-session
41+
/session-status/{token}
42+
```
43+
44+
## Components
45+
46+
### 1. `server.py` — Python Bridge Server
47+
48+
Runs on port 8000 (HTTP + WebSocket on the same port).
49+
50+
**Setup:**
51+
```bash
52+
pip install -r requirements.txt
53+
python server.py
54+
```
55+
56+
**Endpoints:**
57+
- `GET /` → serves `index.html`
58+
- `GET /new-session` → creates a new session, returns `{ sessionToken, pin, qrData }`
59+
- `GET /session-status/{token}` → returns phone connection status
60+
- `GET /health` → health check
61+
- `wss://host/ws/{token}?role=phone|client` → WebSocket bridge
62+
63+
### 2. `index.html` — Web Client
64+
65+
Single-file, no build step required. Open directly in a browser or via `https://yourserver.com/`.
66+
67+
Features:
68+
- Pairing screen with QR code + 6-digit PIN
69+
- Full conversation list + thread view
70+
- Real-time messaging via WebSocket
71+
- Dark theme, fully responsive
72+
- Session saved in `localStorage` for reconnection
73+
74+
### 3. Android App (`android/`)
75+
76+
77+
Package: `com.nexusbridge.smsbridge`
78+
79+
**Features:**
80+
- D-pad navigable numpad for PIN entry
81+
- ZXing QR code scanner
82+
- Foreground service maintaining persistent WebSocket connection
83+
- Reads SMS/MMS via ContentProvider
84+
- Sends SMS via SmsManager
85+
- BroadcastReceiver for incoming SMS
86+
- Exponential backoff reconnection
87+
88+
**Build:**
89+
Open `android/` folder in Android Studio and run on device.
90+
91+
**Required Permissions:** READ_SMS, SEND_SMS, RECEIVE_SMS, READ_CONTACTS, INTERNET, FOREGROUND_SERVICE, POST_NOTIFICATIONS, CAMERA
92+
93+
## Message Protocol
94+
95+
All WebSocket messages are JSON:
96+
97+
```json
98+
{ "type": "...", "payload": { ... } }
99+
```
100+
101+
| Type | Direction | Description |
102+
|--------------------|-------------------|--------------------------------------|
103+
| `sms_list` | phone → client | Full conversation list |
104+
| `sms_thread` | client → phone, phone → client | Request/response for thread messages |
105+
| `sms_send` | client → phone | Send SMS `{ to, body }` |
106+
| `sms_incoming` | phone → client | New inbound SMS notification |
107+
| `read_receipt` | client → phone | Mark thread as read |
108+
| `typing_indicator` | client → phone | Typing notification |
109+
| `mms_attachment` | phone → client | Base64 MMS attachment |
110+
| `contacts_list` | phone → client | Contact directory |
111+
| `ping` / `pong` | both | 30-second keepalive |
112+
| `phone_connected` | server → client | Phone joined the session |
113+
| `phone_disconnected` | server → client | Phone disconnected |
114+
| `connection_status`| server → client | Current phone connection state |
115+
116+
## Session Flow
117+
118+
1. Web client calls `GET /new-session` → gets `sessionToken` + `pin` + `qrData`
119+
2. Web client connects WebSocket as `role=client`
120+
3. Web client polls `GET /session-status/{token}` every 2 seconds
121+
4. Android app scans QR or enters PIN → connects WebSocket as `role=phone`
122+
5. Server links phone ↔ client, notifies client via `phone_connected`
123+
6. All messages relay bidirectionally in real time

android/app/build.gradle

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
plugins {
2+
id 'com.android.application'
3+
id 'org.jetbrains.kotlin.android'
4+
id 'androidx.navigation.safeargs.kotlin'
5+
}
6+
7+
android {
8+
namespace 'com.nexusbridge.smsbridge'
9+
compileSdk 34
10+
11+
defaultConfig {
12+
applicationId "com.nexusbridge.smsbridge"
13+
minSdk 26
14+
targetSdk 34
15+
versionCode 1
16+
versionName "1.0.0"
17+
18+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
19+
}
20+
21+
buildTypes {
22+
release {
23+
minifyEnabled false
24+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
25+
}
26+
}
27+
28+
compileOptions {
29+
sourceCompatibility JavaVersion.VERSION_1_8
30+
targetCompatibility JavaVersion.VERSION_1_8
31+
}
32+
33+
kotlinOptions {
34+
jvmTarget = '1.8'
35+
}
36+
37+
buildFeatures {
38+
viewBinding true
39+
}
40+
}
41+
42+
dependencies {
43+
// Core Android
44+
implementation 'androidx.core:core-ktx:1.12.0'
45+
implementation 'androidx.appcompat:appcompat:1.6.1'
46+
implementation 'com.google.android.material:material:1.10.0'
47+
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
48+
49+
// Navigation
50+
implementation 'androidx.navigation:navigation-fragment-ktx:2.8.5'
51+
implementation 'androidx.navigation:navigation-ui-ktx:2.8.5'
52+
53+
// Lifecycle
54+
implementation 'androidx.lifecycle:lifecycle-service:2.8.7'
55+
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.7'
56+
57+
// OkHttp (WebSocket)
58+
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
59+
60+
// Gson (JSON)
61+
implementation 'com.google.code.gson:gson:2.10.1'
62+
63+
// ZXing QR scan
64+
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
65+
implementation 'com.google.zxing:core:3.5.2'
66+
67+
// Coroutines
68+
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
69+
70+
testImplementation 'junit:junit:4.13.2'
71+
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
72+
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
73+
}

android/app/proguard-rules.pro

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-keep class com.nexusbridge.smsbridge.** { *; }
2+
-keep class com.google.gson.** { *; }
3+
-keep class okhttp3.** { *; }
4+
-dontwarn okhttp3.**
5+
-dontwarn okio.**
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools">
4+
5+
<!-- SMS / MMS -->
6+
<uses-permission android:name="android.permission.READ_SMS" />
7+
<uses-permission android:name="android.permission.SEND_SMS" />
8+
<uses-permission android:name="android.permission.RECEIVE_SMS" />
9+
<uses-permission android:name="android.permission.READ_MMS" />
10+
<uses-permission android:name="android.permission.RECEIVE_MMS" />
11+
12+
<!-- Contacts -->
13+
<uses-permission android:name="android.permission.READ_CONTACTS" />
14+
15+
<!-- Network -->
16+
<uses-permission android:name="android.permission.INTERNET" />
17+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
18+
19+
<!-- Foreground Service -->
20+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
21+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
22+
23+
<!-- Notifications (Android 13+) -->
24+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
25+
26+
<!-- Camera (QR scan) -->
27+
<uses-permission android:name="android.permission.CAMERA" />
28+
<uses-feature android:name="android.hardware.camera" android:required="false" />
29+
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
30+
31+
<!-- Wake lock to keep service awake -->
32+
<uses-permission android:name="android.permission.WAKE_LOCK" />
33+
34+
<!-- Default SMS (optional — comment out if not default SMS app) -->
35+
<!-- <uses-permission android:name="android.permission.WRITE_SMS" /> -->
36+
37+
<application
38+
android:allowBackup="true"
39+
android:dataExtractionRules="@xml/data_extraction_rules"
40+
android:fullBackupContent="@xml/backup_rules"
41+
android:icon="@mipmap/ic_launcher"
42+
android:label="@string/app_name"
43+
android:roundIcon="@mipmap/ic_launcher_round"
44+
android:supportsRtl="true"
45+
android:theme="@style/Theme.NexusBridge"
46+
tools:targetApi="31">
47+
48+
<!-- Main Activity -->
49+
<activity
50+
android:name=".MainActivity"
51+
android:exported="true"
52+
android:label="@string/app_name"
53+
android:launchMode="singleTop">
54+
<intent-filter>
55+
<action android:name="android.intent.action.MAIN" />
56+
<category android:name="android.intent.category.LAUNCHER" />
57+
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
58+
</intent-filter>
59+
</activity>
60+
61+
<!-- Bridge Foreground Service -->
62+
<service
63+
android:name=".BridgeService"
64+
android:exported="false"
65+
android:foregroundServiceType="dataSync" />
66+
67+
<!-- SMS Broadcast Receiver -->
68+
<receiver
69+
android:name=".SmsReceiver"
70+
android:exported="true"
71+
android:permission="android.permission.BROADCAST_SMS">
72+
<intent-filter android:priority="999">
73+
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
74+
</intent-filter>
75+
</receiver>
76+
77+
</application>
78+
</manifest>

0 commit comments

Comments
 (0)