Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
fc31a5e
Patch manifest
haslinghuis Oct 29, 2025
ae221c4
Updte workflow
haslinghuis Oct 29, 2025
b6b31db
Use tauri-plugin-serialplugin
haslinghuis Oct 29, 2025
74a3266
Fix build
haslinghuis Oct 29, 2025
983a9cf
Use Kotlin DSL syntax, not Groovy
haslinghuis Oct 29, 2025
c2163bd
Replace sed with awk
haslinghuis Oct 29, 2025
0533869
Downgrade tauri-plugin-serialplugin to 2.10.0 in Cargo.lock
haslinghuis Oct 29, 2025
a801f59
Upgrade to tauri-plugin-serialplugin 2.16.0 for Android USB support
haslinghuis Oct 29, 2025
cadf39f
Add Android USB debugging tools and updated documentation
haslinghuis Oct 29, 2025
eb441f4
Fix manifest patching: replace sed with awk for cross-platform compat…
haslinghuis Oct 29, 2025
8c93e03
Add custom MainActivity with USB runtime permission handling
haslinghuis Oct 29, 2025
b1c3154
Update debugging docs with MainActivity testing instructions
haslinghuis Oct 29, 2025
0a60f3e
Fix MainActivity import: add missing TauriActivity import
haslinghuis Oct 29, 2025
2eb9aaa
Nitpicks
haslinghuis Oct 29, 2025
cd6aa1f
Fix JitPack repository injection: use settings.gradle.kts with Kotlin…
haslinghuis Oct 29, 2025
2f2e9ab
Move JitPack repository injection into patch script
haslinghuis Oct 29, 2025
e9c511e
Inject JitPack into dependencyResolutionManagement.repositories in se…
haslinghuis Oct 29, 2025
3d13b6e
Revert usb-serial-for-android to 3.8.0 and harden JitPack injection
haslinghuis Oct 29, 2025
d71a7c1
Fix grep alternation and duplicate -n in JitPack repo preview
haslinghuis Oct 29, 2025
04b8256
Force usb-serial-for-android to 3.8.0 and remove FAIL_ON_PROJECT_REPO…
haslinghuis Oct 29, 2025
99642e8
Fix Kotlin DSL syntax error - use resolutionStrategy instead of isForce
haslinghuis Oct 29, 2025
aa9ddc8
Add debug output to show settings.gradle.kts before and after JitPack…
haslinghuis Oct 29, 2025
8d87c20
Add JitPack repository directly to app build.gradle.kts as fallback
haslinghuis Oct 29, 2025
37fb1b6
Remove custom MainActivity - not needed for USB serial permissions
haslinghuis Oct 29, 2025
6d79317
Add extensive debug logging for Android USB enumeration
haslinghuis Oct 29, 2025
a826b93
Add ADB wireless debugging setup for Android USB testing
haslinghuis Oct 29, 2025
4a32b85
Fix ADB wireless setup script to support IPv6 addresses
haslinghuis Oct 29, 2025
b85212b
Add comprehensive next steps guide for Android USB debugging
haslinghuis Oct 29, 2025
792d918
Add local Android build script: init + patch + dev/release + signing …
haslinghuis Oct 29, 2025
735ac48
feat(android): enable USB serial support with debugging capabilities
haslinghuis Oct 29, 2025
1d19205
docs: add Android USB serial status and progress tracking
haslinghuis Oct 29, 2025
9416e32
Shell improvement
haslinghuis Oct 29, 2025
1331237
Default is not needed when using explicit permissions
haslinghuis Oct 29, 2025
4435a99
No need for HMR_HOST
haslinghuis Oct 29, 2025
bb84552
Forgot to include
haslinghuis Oct 29, 2025
90cecf7
Address 'Warning: src-tauri/gen/android/settings.gradle.kts not found…
haslinghuis Oct 30, 2025
83122fc
Address 'Warning: src-tauri/gen/android/settings.gradle.kts not found…
haslinghuis Oct 30, 2025
7e03458
little cleanup
haslinghuis Oct 30, 2025
b7f391a
Update scripts
haslinghuis Nov 3, 2025
0a0bf83
Fix android debugging
haslinghuis Nov 3, 2025
62f9c41
???
haslinghuis Nov 3, 2025
05bc006
Add dependency for usb-serial-for-android
haslinghuis Nov 3, 2025
5ec9332
Address CR nitpick
haslinghuis Nov 3, 2025
cb3680a
After running cargo update
haslinghuis Nov 3, 2025
5f31190
Fix working versions
haslinghuis Nov 3, 2025
a93186b
Dependency hell
haslinghuis Nov 3, 2025
8e779cd
Which version works ???
haslinghuis Nov 3, 2025
72173cf
Fix script portability
haslinghuis Nov 3, 2025
56f041f
modify the patch script to add the repository directly to build.gradl…
haslinghuis Nov 3, 2025
4dfd15e
refactor scripts for single responsibility
haslinghuis Nov 3, 2025
c19e84c
Using js API instead of Rust invoke
haslinghuis Nov 6, 2025
bf62596
Fix USB permission issue
haslinghuis Nov 6, 2025
220037c
Fix port enumeration
haslinghuis Nov 6, 2025
0e4c4c7
Some cleanup
haslinghuis Nov 6, 2025
6dea4b3
Read is not solved
haslinghuis Nov 6, 2025
7e5ce52
Apply custom MainActivity.kt
haslinghuis Nov 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,11 @@ jobs:
- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Setup Java 17
- name: Setup Java 21
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
java-version: '21'

- name: Setup Android SDK
uses: android-actions/setup-android@v3
Expand All @@ -151,6 +151,9 @@ jobs:
- name: Initialize Tauri Android project
run: yarn tauri android init --ci

- name: Patch Android manifest for USB support
run: bash scripts/tauri-patch-android.sh

- name: Build Tauri Android APK
uses: tauri-apps/tauri-action@v0
with:
Expand Down
6 changes: 0 additions & 6 deletions ANDROID.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,12 +319,6 @@ mkdir -p dist && touch dist/.gitkeep

## Available Scripts

### Emulator Management
- `yarn android:emu:list` - List all AVDs
- `yarn android:emu:check` - Check if emulator is running
- `yarn android:emu:start` - Start emulator with SwiftShader
- `yarn android:emu:start:host` - Start with host GPU

### Tauri Android
- `yarn tauri:dev:android` - Development build with hot reload
- `yarn tauri:build:android` - Production release build
Expand Down
45 changes: 0 additions & 45 deletions android-env.sh

This file was deleted.

24 changes: 2 additions & 22 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,12 @@
"android:run": "vite build && node capacitor.config.generator.mjs && npx cap run android",
"android:sync": "vite build && node capacitor.config.generator.mjs && npx cap sync android",
"android:release": "vite build && node capacitor.config.generator.mjs && npx cap build android --release",
"android:emu:list": "run-script-os",
"android:emu:list:win32": "%ANDROID_HOME%\\emulator\\emulator.exe -list-avds",
"android:emu:list:default": "$ANDROID_HOME/emulator/emulator -list-avds",
"android:emu:check": "run-script-os",
"android:emu:check:win32": "%ANDROID_HOME%\\platform-tools\\adb.exe devices",
"android:emu:check:default": "$ANDROID_HOME/platform-tools/adb devices | grep -q emulator && echo 'Emulator running' || echo 'No emulator'",
"android:emu:start": "run-script-os",
"android:emu:start:win32": "start \"\" \"%ANDROID_HOME%\\emulator\\emulator.exe\" -avd Medium_Phone_API_35 -gpu swiftshader_indirect -no-snapshot-load",
"android:emu:start:default": "QT_QPA_PLATFORM=xcb $ANDROID_HOME/emulator/emulator -avd ${AVD:-Medium_Phone_API_35} -gpu swiftshader_indirect -no-snapshot-load &",
"android:emu:start:host": "run-script-os",
"android:emu:start:host:win32": "start \"\" \"%ANDROID_HOME%\\emulator\\emulator.exe\" -avd Medium_Phone_API_35 -gpu host -no-snapshot-load",
"android:emu:start:host:default": "QT_QPA_PLATFORM=xcb $ANDROID_HOME/emulator/emulator -avd ${AVD:-Medium_Phone_API_35} -gpu host -no-snapshot-load &",
"android:adb:wait": "run-script-os",
"android:adb:wait:win32": "%ANDROID_HOME%\\platform-tools\\adb.exe wait-for-device && %ANDROID_HOME%\\platform-tools\\adb.exe shell \"while [ $(getprop sys.boot_completed) != 1 ]; do sleep 1; done\"",
"android:adb:wait:default": "$ANDROID_HOME/platform-tools/adb wait-for-device && until $ANDROID_HOME/platform-tools/adb shell getprop sys.boot_completed 2>/dev/null | grep -q 1; do sleep 1; done",
"android:adb:reverse": "run-script-os",
"android:adb:reverse:win32": "%ANDROID_HOME%\\platform-tools\\adb.exe reverse tcp:8000 tcp:8000",
"android:adb:reverse:default": "$ANDROID_HOME/platform-tools/adb reverse tcp:8000 tcp:8000",
"format": "prettier --write {src,test}/**/*.{js,vue,css,less}",
"storybook": "start-storybook -p 6006",
"prepare": "husky install",
"tauri:dev": "tauri dev",
"tauri:build": "tauri build",
"tauri:dev:android": "run-script-os",
"tauri:dev:android:win32": "yarn android:emu:start && yarn android:adb:wait && yarn android:adb:reverse && tauri android dev",
"tauri:dev:android:default": "yarn android:emu:start && yarn android:adb:wait && yarn android:adb:reverse && tauri android dev",
"tauri:dev:android:with-dist": "yarn build && yarn tauri:dev:android",
"tauri:dev:android": "tauri android dev",
"tauri:build:android": "tauri android build"
},
"window": {
Expand Down Expand Up @@ -94,6 +73,7 @@
"semver-min": "^0.7.2",
"short-unique-id": "^5.2.0",
"switchery-latest": "^0.8.2",
"tauri-plugin-serialplugin-api": "^2.21.1",
"three": "^0.176.0",
"tiny-emitter": "^2.1.0",
"tippy.js": "^6.3.7",
Expand Down
165 changes: 165 additions & 0 deletions scripts/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package com.betaflight.app

import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbManager
import android.os.Build
import android.os.Bundle
import android.util.Log

class MainActivity : TauriActivity() {
private val TAG = "BetaflightUSB"
private val ACTION_USB_PERMISSION = "com.betaflight.configurator.USB_PERMISSION"

private val usbReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
ACTION_USB_PERMISSION -> {
synchronized(this) {
val device: UsbDevice? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(UsbManager.EXTRA_DEVICE, UsbDevice::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
}

if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
device?.let {
Log.d(TAG, "Permission granted for device ${it.deviceName}")
// Device is ready to use
}
} else {
Log.d(TAG, "Permission denied for device ${device?.deviceName}")
}
}
}
UsbManager.ACTION_USB_DEVICE_ATTACHED -> {
val device: UsbDevice? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(UsbManager.EXTRA_DEVICE, UsbDevice::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
}
device?.let {
Log.d(TAG, "USB device attached: ${it.deviceName}")
requestUsbPermission(it)
}
}
UsbManager.ACTION_USB_DEVICE_DETACHED -> {
val device: UsbDevice? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(UsbManager.EXTRA_DEVICE, UsbDevice::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
}
Log.d(TAG, "USB device detached: ${device?.deviceName}")
}
}
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// Register USB broadcast receiver
val filter = IntentFilter().apply {
addAction(ACTION_USB_PERMISSION)
addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED)
addAction(UsbManager.ACTION_USB_DEVICE_DETACHED)
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(usbReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
} else {
registerReceiver(usbReceiver, filter)
}

// Check if launched by USB device attachment
if (intent.action == UsbManager.ACTION_USB_DEVICE_ATTACHED) {
val device: UsbDevice? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(UsbManager.EXTRA_DEVICE, UsbDevice::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
}
device?.let {
Log.d(TAG, "App launched by USB device: ${it.deviceName}")
requestUsbPermission(it)
}
} else {
// Check for already connected devices
checkConnectedDevices()
}
}

override fun onDestroy() {
super.onDestroy()
unregisterReceiver(usbReceiver)
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)

if (intent.action == UsbManager.ACTION_USB_DEVICE_ATTACHED) {
val device: UsbDevice? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(UsbManager.EXTRA_DEVICE, UsbDevice::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
}
device?.let {
Log.d(TAG, "USB device attached via new intent: ${it.deviceName}")
requestUsbPermission(it)
}
}
}

private fun checkConnectedDevices() {
val usbManager = getSystemService(Context.USB_SERVICE) as UsbManager
val deviceList = usbManager.deviceList

Log.d(TAG, "Checking connected USB devices: ${deviceList.size} found")

deviceList.values.forEach { device ->
Log.d(TAG, "Device: ${device.deviceName}, VID: ${device.vendorId}, PID: ${device.productId}")
if (!usbManager.hasPermission(device)) {
Log.d(TAG, "No permission for ${device.deviceName}, requesting...")
requestUsbPermission(device)
} else {
Log.d(TAG, "Already have permission for ${device.deviceName}")
}
}
}

private fun requestUsbPermission(device: UsbDevice) {
val usbManager = getSystemService(Context.USB_SERVICE) as UsbManager

if (usbManager.hasPermission(device)) {
Log.d(TAG, "Already have permission for ${device.deviceName}")
return
}

// Use FLAG_IMMUTABLE for Android 12+ (API 31+) as required by Android 14+ (API 34+)
// when using implicit intents with PendingIntent
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}

val permissionIntent = PendingIntent.getBroadcast(
this as Context,
0,
Intent(ACTION_USB_PERMISSION),
flags
)

Log.d(TAG, "Requesting USB permission for ${device.deviceName} (VID: ${device.vendorId}, PID: ${device.productId})")
usbManager.requestPermission(device, permissionIntent)
}
}
88 changes: 88 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Android USB Support Scripts

This directory contains scripts to configure Android USB serial support for the Betaflight Configurator Tauri app.

## Scripts Overview

### `tauri-patch-android.sh` (Main Orchestrator)
The main script that coordinates all Android USB configuration. Run this after `cargo tauri android init`.

**What it does:**
- Calls all individual scripts in the correct order
- Provides overall progress reporting
- Ensures Android project exists before proceeding

### `patch-android-manifest.sh`
Configures the Android manifest with USB permissions and intent filters.

**What it does:**
- Adds `android.permission.USB_PERMISSION` permission
- Adds `android.hardware.usb.host` feature
- Adds USB device attach intent filter
- Adds metadata referencing the device filter XML

### `create-device-filter.sh`
Creates the USB device filter XML file that defines supported USB devices.

**What it does:**
- Creates `res/xml/device_filter.xml`
- Defines USB device filters for Betaflight-compatible devices:
- FT232R USB UART
- STM32 devices (various modes)
- CP210x devices
- GD32 devices
- AT32 devices
- APM32 devices
- Raspberry Pi Pico devices

### `patch-gradle-settings.sh`
Configures project-level Gradle repositories in `settings.gradle.kts`.

**What it does:**
- Adds jitpack.io repository to `dependencyResolutionManagement`
- Adds jitpack.io repository to `pluginManagement`
- Ensures Google Maven and Maven Central are available

### `patch-app-gradle.sh`
Configures app-level Gradle dependencies and repositories.

**What it does:**
- Adds jitpack.io repository to app module
- Adds `usb-serial-for-android:3.8.0` dependency
- Ensures dependency resolution works at module level

## Usage

After running `cargo tauri android init`, execute:

```bash
bash scripts/tauri-patch-android.sh
```

This will run all configuration steps automatically.

## Individual Script Usage

You can also run individual scripts if needed:

```bash
# Only update manifest permissions
bash scripts/patch-android-manifest.sh

# Only update device filters
bash scripts/create-device-filter.sh

# Only update Gradle settings
bash scripts/patch-gradle-settings.sh

# Only update app dependencies
bash scripts/patch-app-gradle.sh
```

## Maintenance Benefits

- **Separation of Concerns**: Each script handles one specific aspect
- **Independent Testing**: Scripts can be tested and debugged individually
- **Selective Updates**: Only run the scripts that need changes
- **Clear Documentation**: Each script has a focused purpose
- **Easier Maintenance**: Changes to one aspect don't affect others
Loading