Merge branch 'main' into bscotch/android-signing-ci

This commit is contained in:
lucas lelievre
2025-12-09 14:51:16 +01:00
committed by GitHub
6 changed files with 74 additions and 28 deletions

View File

@@ -13,7 +13,7 @@ plugins {
kotlin("plugin.serialization")
id("com.github.gmazzo.buildconfig")
id("com.android.application") version "8.6.1"
id("com.android.application") version "8.13.1"
id("org.ajoberstar.grgit")
}
@@ -79,17 +79,17 @@ dependencies {
implementation("org.apache.commons:commons-lang3:3.15.0")
// Android stuff
implementation("androidx.appcompat:appcompat:1.7.0")
implementation("androidx.core:core-ktx:1.13.1")
implementation("com.google.android.material:material:1.12.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("androidx.core:core-ktx:1.17.0")
implementation("com.google.android.material:material:1.13.0")
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
androidTestImplementation("androidx.test.ext:junit:1.3.0")
androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")
// For hosting web GUI
implementation("io.ktor:ktor-server-core:2.3.12")
implementation("io.ktor:ktor-server-netty:2.3.10")
implementation("io.ktor:ktor-server-caching-headers:2.3.12")
implementation("io.ktor:ktor-server-core:2.3.13")
implementation("io.ktor:ktor-server-netty:2.3.13")
implementation("io.ktor:ktor-server-caching-headers:2.3.13")
// Serial
implementation("com.github.mik3y:usb-serial-for-android:3.7.0")
@@ -109,7 +109,7 @@ android {
compile your app. This means your app can use the API features included in
this API level and lower. */
compileSdk = 35
compileSdk = 36
/* The defaultConfig block encapsulates default settings and entries for all
build variants and can override some attributes in main/AndroidManifest.xml
@@ -129,7 +129,7 @@ android {
minSdk = 26
// Specifies the API level used to test the app.
targetSdk = 35
targetSdk = 36
// adds an offset of the version code as we might do apk releases in the middle of actual
// releases if we failed on bundling or stuff
@@ -163,10 +163,11 @@ android {
/* By default, Android Studio configures the release build type to enable code
shrinking, using minifyEnabled, and specifies the default ProGuard rules file. */
getByName("release") {
release {
isMinifyEnabled = true // Enables code shrinking for the release build type.
isShrinkResources = true // Enables resource shrinking.
proguardFiles(
getDefaultProguardFile("proguard-android.txt"),
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
signingConfig = signingConfigs.getByName("release")
@@ -177,6 +178,7 @@ android {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}

View File

@@ -19,21 +19,31 @@ import io.ktor.server.engine.embeddedServer
import io.ktor.server.http.content.CachingOptions
import io.ktor.server.http.content.staticResources
import io.ktor.server.netty.Netty
import io.ktor.server.netty.NettyApplicationEngine
import io.ktor.server.plugins.cachingheaders.CachingHeaders
import io.ktor.server.routing.routing
import kotlinx.coroutines.runBlocking
import java.io.File
import java.time.ZonedDateTime
import kotlin.concurrent.thread
import kotlin.system.exitProcess
lateinit var webServer: NettyApplicationEngine
private set
val webServerInitialized: Boolean
get() = ::webServer.isInitialized
var webServerPort = 0
lateinit var vrServer: VRServer
private set
val vrServerInitialized: Boolean
get() = ::vrServer.isInitialized
fun main(activity: AppCompatActivity) {
fun startWebServer() {
// Host the web GUI server
embeddedServer(Netty, port = 34536) {
webServer = embeddedServer(Netty, port = 0) {
routing {
install(CachingHeaders) {
options { _, _ ->
@@ -43,7 +53,10 @@ fun main(activity: AppCompatActivity) {
staticResources("/", "web-gui", "index.html")
}
}.start(wait = false)
webServerPort = runBlocking { webServer.resolvedConnectors().first().port }
}
fun startVRServer(activity: AppCompatActivity) {
thread(start = true, name = "Main VRServer Thread") {
try {
LogManager.initialize(activity.filesDir)

View File

@@ -7,6 +7,8 @@ import android.webkit.WebSettings
import android.webkit.WebView
import androidx.appcompat.app.AppCompatActivity
import io.eiren.util.logging.LogManager
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
class AndroidJsObject {
@JavascriptInterface
@@ -25,12 +27,22 @@ class MainActivity : AppCompatActivity() {
e1.printStackTrace()
}
// Start the server if it isn't already running
if (!vrServerInitialized) {
LogManager.info("[MainActivity] VRServer isn't running yet, starting it...")
main(this)
} else {
LogManager.info("[MainActivity] VRServer is already running, skipping initialization.")
initLock.withLock {
// Start the GUI if it isn't already running
if (!webServerInitialized) {
LogManager.info("[MainActivity] WebServer isn't running yet, starting it...")
startWebServer()
} else {
LogManager.info("[MainActivity] WebServer is already running, skipping initialization.")
}
// Start the server if it isn't already running
if (!vrServerInitialized) {
LogManager.info("[MainActivity] VRServer isn't running yet, starting it...")
startVRServer(this)
} else {
LogManager.info("[MainActivity] VRServer is already running, skipping initialization.")
}
}
// Load the web GUI web page
@@ -59,7 +71,7 @@ class MainActivity : AppCompatActivity() {
guiWebView.clearCache(true)
// Load GUI page
guiWebView.loadUrl("http://127.0.0.1:34536/")
guiWebView.loadUrl("http://127.0.0.1:$webServerPort/")
LogManager.info("[MainActivity] GUI WebView has been initialized and loaded.")
// Start a foreground service to notify the user the SlimeVR Server is running
@@ -67,4 +79,8 @@ class MainActivity : AppCompatActivity() {
val serviceIntent = Intent(this, ForegroundService::class.java)
startForegroundService(serviceIntent)
}
companion object {
val initLock = ReentrantLock()
}
}

View File

@@ -17,6 +17,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.*;
import java.util.Comparator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -122,7 +123,10 @@ public class ConfigManager {
var cfgFileMaybeFolder = cfgFile.toFile();
if (cfgFileMaybeFolder.isDirectory()) {
try (Stream<Path> pathStream = Files.walk(cfgFile)) {
var list = pathStream.sorted(Comparator.reverseOrder()).toList();
// Can't use .toList() on Android
var list = pathStream
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
for (var path : list) {
Files.delete(path);
}

View File

@@ -24,6 +24,7 @@ import java.security.MessageDigest
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
import java.util.stream.Collectors
import kotlin.concurrent.scheduleAtFixedRate
data class DownloadedFirmwarePart(
@@ -119,7 +120,8 @@ class FirmwareUpdateHandler(private val server: VRServer) :
ssid: String,
password: String,
) {
val serialPort = this.server.serialHandler.knownPorts.toList()
// Can't use .toList() on Android
val serialPort = this.server.serialHandler.knownPorts.collect(Collectors.toList())
.find { port -> deviceId.id == port.portLocation }
if (serialPort == null) {

View File

@@ -3,6 +3,8 @@ package dev.slimevr.firmware
import io.eiren.util.logging.LogManager
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.EOFException
import java.io.IOException
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetAddress
@@ -99,11 +101,12 @@ class OTAUpdateTask(
}
private fun upload(serverSocket: ServerSocket): Boolean {
var connection: Socket? = null
try {
LogManager.info("[OTAUpdate] Starting on: ${serverSocket.localPort}")
LogManager.info("[OTAUpdate] Waiting for device...")
val connection = serverSocket.accept()
connection = serverSocket.accept()
this.uploadSocket = connection
connection.setSoTimeout(1000)
val dos = DataOutputStream(connection.getOutputStream())
@@ -130,7 +133,11 @@ class OTAUpdateTask(
// so we simply skip it.
// The reason those bytes are skipped here is to not have to skip all of them when checking
// for the OK response. Saving time
dis.skipNBytes(4)
val bytesSkipped = dis.skipBytes(4)
// Replicate behaviour of .skipNBytes()
if (bytesSkipped != 4) {
throw IOException("Unexpected number of bytes skipped: $bytesSkipped")
}
}
if (canceled) return false
@@ -138,13 +145,15 @@ class OTAUpdateTask(
// We set the timeout of the connection bigger as it can take some time for the MCU
// to confirm that everything is ok
connection.setSoTimeout(10000)
val responseBytes = dis.readAllBytes()
val responseBytes = dis.readBytes()
val response = String(responseBytes)
return response.contains("OK")
} catch (e: Exception) {
LogManager.severe("Unable to upload the firmware using ota", e)
return false
} finally {
connection?.close()
}
}