mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Merge branch 'main' into bscotch/android-signing-ci
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user