fix: add microphone foreground service type for AssistantService STT

Android 14+ requires explicit foregroundServiceType declaration for
microphone access in foreground services. Without it, SpeechRecognizer
silently fails on the second turn of the voice loop.

Also centralizes Android SDK versions (compileSdk, minSdk, targetSdk)
into libs.versions.toml and bumps minSdk from 28 to 29.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kohei 2026-02-21 20:26:09 +09:00
parent 18309c3f9d
commit 7b13171689
8 changed files with 23 additions and 13 deletions

View file

@ -15,12 +15,12 @@ val keystoreProperties = Properties().apply {
android { android {
namespace = "io.picoclaw.android" namespace = "io.picoclaw.android"
compileSdk = 36 compileSdk = libs.versions.android.compileSdk.get().toInt()
defaultConfig { defaultConfig {
applicationId = "io.picoclaw.android" applicationId = "io.picoclaw.android"
minSdk = 28 minSdk = libs.versions.android.minSdk.get().toInt()
targetSdk = 36 targetSdk = libs.versions.android.targetSdk.get().toInt()
versionCode = 1 versionCode = 1
versionName = "1.0.0" versionName = "1.0.0"
} }

View file

@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<queries> <queries>
@ -66,7 +67,7 @@
<service <service
android:name=".assistant.AssistantService" android:name=".assistant.AssistantService"
android:exported="false" android:exported="false"
android:foregroundServiceType="specialUse"> android:foregroundServiceType="microphone|specialUse">
<property <property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="AI voice assistant overlay for real-time conversation" /> android:value="AI voice assistant overlay for real-time conversation" />

View file

@ -2,6 +2,7 @@ package io.picoclaw.android.assistant
import android.app.Notification import android.app.Notification
import android.content.Intent import android.content.Intent
import android.content.pm.ServiceInfo
import android.graphics.PixelFormat import android.graphics.PixelFormat
import android.os.IBinder import android.os.IBinder
import android.view.Gravity import android.view.Gravity
@ -87,7 +88,12 @@ class AssistantService : LifecycleService(), SavedStateRegistryOwner {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId) super.onStartCommand(intent, flags, startId)
startForeground(NOTIFICATION_ID, buildNotification()) startForeground(
NOTIFICATION_ID,
buildNotification(),
ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
or ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
)
// Resolve wsUrl from the main WebSocketClient // Resolve wsUrl from the main WebSocketClient
val mainWsClient: WebSocketClient by inject() val mainWsClient: WebSocketClient by inject()

View file

@ -6,10 +6,10 @@ plugins {
android { android {
namespace = "io.picoclaw.android.core.data" namespace = "io.picoclaw.android.core.data"
compileSdk = 36 compileSdk = libs.versions.android.compileSdk.get().toInt()
defaultConfig { defaultConfig {
minSdk = 28 minSdk = libs.versions.android.minSdk.get().toInt()
} }
compileOptions { compileOptions {

View file

@ -4,10 +4,10 @@ plugins {
android { android {
namespace = "io.picoclaw.android.core.domain" namespace = "io.picoclaw.android.core.domain"
compileSdk = 36 compileSdk = libs.versions.android.compileSdk.get().toInt()
defaultConfig { defaultConfig {
minSdk = 28 minSdk = libs.versions.android.minSdk.get().toInt()
} }
compileOptions { compileOptions {

View file

@ -5,10 +5,10 @@ plugins {
android { android {
namespace = "io.picoclaw.android.core.ui" namespace = "io.picoclaw.android.core.ui"
compileSdk = 36 compileSdk = libs.versions.android.compileSdk.get().toInt()
defaultConfig { defaultConfig {
minSdk = 28 minSdk = libs.versions.android.minSdk.get().toInt()
} }
buildFeatures { buildFeatures {

View file

@ -5,10 +5,10 @@ plugins {
android { android {
namespace = "io.picoclaw.android.feature.chat" namespace = "io.picoclaw.android.feature.chat"
compileSdk = 36 compileSdk = libs.versions.android.compileSdk.get().toInt()
defaultConfig { defaultConfig {
minSdk = 28 minSdk = libs.versions.android.minSdk.get().toInt()
} }
buildFeatures { buildFeatures {

View file

@ -1,4 +1,7 @@
[versions] [versions]
android-compileSdk = "36"
android-minSdk = "29"
android-targetSdk = "36"
agp = "9.0.1" agp = "9.0.1"
kotlin = "2.3.0" kotlin = "2.3.0"
ksp = "2.3.6" ksp = "2.3.6"