mirror of
https://github.com/twinksoftware/Gearslip.git
synced 2026-11-10 16:02:25 +01:00
Gearslip now reads messages aloud and takes replies by voice. It asks whether to reply, reads the reply back and sends it after a countdown. Say "cancel" or "change it" to stop or redo it, or tap a quick reply. Speech-to-text runs on the phone with whisper.cpp and a small Whisper model, so voice works without Google services. The model ships in the repo, so a fresh clone builds with nothing to download. New messages show as one card at the top right, with a bar counting down to when it closes or sends. Other changes: - Steering wheel buttons, knobs and D-pads drive the car screen. - Smoother car audio: chunks wait for the car instead of dropping. - Sharper, more responsive video. - The Gearslip switch in Settings now stops it taking the USB connection. - Apps that refuse Gearslip say so instead of loading forever. - The phone asks for notification permission. - The home screen stays upright. - The car screen shows what's new after an update.
147 lines
5.6 KiB
Kotlin
147 lines
5.6 KiB
Kotlin
import java.util.Properties
|
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
|
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
|
|
|
|
/**
|
|
* Gearslip: a phone-side Android Auto client built against the reverse-engineered AA wire
|
|
* protocol, so a non-Google app can authenticate to and project onto a real car head unit
|
|
* with no gearhead in the path. No third-party dependencies: the X.509 certificate is
|
|
* generated by hand (see `SelfSignedCert`) so there is no BouncyCastle version to track and
|
|
* no second crypto provider to collide with Conscrypt.
|
|
*/
|
|
plugins {
|
|
alias(libs.plugins.android.application)
|
|
alias(libs.plugins.kotlin.compose)
|
|
}
|
|
|
|
// The release signing key, read from an untracked keystore.properties beside this file or at the
|
|
// repository root. Third-party apps allowlist Gearslip by the SHA-256 of this certificate, so the
|
|
// key must stay the same for the life of the project - and must never be committed.
|
|
val keystoreProperties = Properties().apply {
|
|
listOf(
|
|
rootProject.file("keystore.properties"),
|
|
project.file("keystore.properties"),
|
|
).firstOrNull { it.exists() }?.inputStream()?.use { load(it) }
|
|
}
|
|
|
|
android {
|
|
namespace = "app.seb3thehacker.gearslip"
|
|
compileSdk = 37
|
|
|
|
defaultConfig {
|
|
applicationId = "app.seb3thehacker.gearslip"
|
|
minSdk = 31
|
|
targetSdk = 37
|
|
versionCode = 6
|
|
versionName = "0.1.0-alpha"
|
|
manifestPlaceholders["appLabel"] = "Gearslip"
|
|
}
|
|
|
|
signingConfigs {
|
|
// Declared only when the properties file is present, so a clone without the key still
|
|
// builds debug; assembleRelease then falls back to unsigned rather than failing here.
|
|
if (keystoreProperties.getProperty("storeFile") != null) {
|
|
create("release") {
|
|
storeFile = rootProject.file(keystoreProperties.getProperty("storeFile"))
|
|
storePassword = keystoreProperties.getProperty("storePassword")
|
|
keyAlias = keystoreProperties.getProperty("keyAlias")
|
|
keyPassword = keystoreProperties.getProperty("keyPassword")
|
|
}
|
|
}
|
|
}
|
|
|
|
buildTypes {
|
|
release {
|
|
signingConfig = signingConfigs.findByName("release")
|
|
}
|
|
// Its own package, so a dev build installs beside the release one instead of over it.
|
|
// Signed with the release key when it's here: both builds declare the car template
|
|
// permission, and Android refuses a second app declaring it under a different key.
|
|
debug {
|
|
applicationIdSuffix = ".dev"
|
|
signingConfigs.findByName("release")?.let { signingConfig = it }
|
|
manifestPlaceholders["appLabel"] = "Gearslip Dev"
|
|
}
|
|
}
|
|
|
|
buildFeatures {
|
|
buildConfig = true
|
|
}
|
|
|
|
// Keeps build output out of the module folder: <root>/build/gearslip/... instead of
|
|
// <root>/gearslip/build/..., so everything this project produces lands under one
|
|
// root-level build/ directory.
|
|
layout.buildDirectory.set(rootProject.layout.buildDirectory.dir("gearslip"))
|
|
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_21
|
|
targetCompatibility = JavaVersion.VERSION_21
|
|
}
|
|
|
|
defaultConfig {
|
|
// Speech-to-text ships for 64-bit ARM only: every phone Gearslip runs on, and one copy
|
|
// of the native library instead of four.
|
|
ndk { abiFilters += "arm64-v8a" }
|
|
}
|
|
ndkVersion = "27.1.12297006"
|
|
externalNativeBuild {
|
|
cmake {
|
|
path = file("src/main/cpp/CMakeLists.txt")
|
|
version = "3.22.1"
|
|
}
|
|
}
|
|
androidResources {
|
|
// Model weights barely compress, and stored plainly they stream straight out of the APK.
|
|
noCompress += "bin"
|
|
}
|
|
|
|
testOptions {
|
|
// GearslipLog calls android.util.Log, which is a stub on the local JVM.
|
|
unitTests.isReturnDefaultValues = true
|
|
}
|
|
|
|
}
|
|
|
|
// Renames the produced APK and moves it to the root build/ folder (<root>/build/Gearslip-debug.apk)
|
|
// after the fact rather than fighting AGP's variant-output API across versions - simpler and
|
|
// doesn't break when that API moves again.
|
|
tasks.register("renameApk") {
|
|
doLast {
|
|
val rootBuildDir = rootProject.layout.buildDirectory.get().asFile
|
|
layout.buildDirectory.dir("outputs/apk").get().asFile.walkTopDown()
|
|
.filter { it.extension == "apk" }
|
|
.toList()
|
|
.forEach { apk ->
|
|
val renamed = File(rootBuildDir, "Gearslip-${apk.parentFile.name}.apk")
|
|
apk.copyTo(renamed, overwrite = true)
|
|
apk.delete()
|
|
}
|
|
}
|
|
}
|
|
tasks.matching { it.name.startsWith("assemble") }.configureEach { finalizedBy("renameApk") }
|
|
|
|
dependencies {
|
|
// Only for the XML window theme (Theme.Material3.DynamicColors); every screen is Compose.
|
|
implementation(libs.google.material)
|
|
implementation(platform(libs.androidx.compose.bom))
|
|
implementation(libs.androidx.compose.ui)
|
|
implementation(libs.androidx.compose.material3)
|
|
implementation(libs.androidx.compose.material.icons.core)
|
|
implementation(libs.androidx.activity.compose)
|
|
implementation(libs.androidx.lifecycle.runtime.compose)
|
|
// Used from the host side, not the app side: Gearslip renders other apps' templates rather
|
|
// than publishing its own, so this is here for the AIDL interfaces and the model classes
|
|
// their Bundleables deserialize into.
|
|
implementation(libs.androidx.car.app)
|
|
implementation(libs.androidx.car.app.projected)
|
|
implementation(libs.androidx.media)
|
|
testImplementation(libs.junit)
|
|
}
|
|
|
|
tasks.withType<KotlinJvmCompile>().configureEach {
|
|
compilerOptions {
|
|
jvmTarget.set(JvmTarget.JVM_21)
|
|
}
|
|
}
|
|
|