From 9e538e49222a806a952c72313c67221e44c47f5a Mon Sep 17 00:00:00 2001 From: hatsch Date: Tue, 3 Feb 2026 08:42:26 +0100 Subject: [PATCH 1/2] Harden service startup for Android 14+ boot restrictions - Catch ForegroundServiceStartNotAllowedException, SecurityException, and IllegalStateException when starting foreground services - Show notification when service fails to start at boot, tapping opens app which automatically restarts the service via onResume - Dismiss failure notification when service starts successfully - Add service alert notification channel and localized strings - Update CLAUDE.md with translations workflow note - Update README.md with local model import feature Co-Authored-By: Claude Opus 4.5 --- CLAUDE.md | 11 +++++ README.md | 5 ++- .../java/com/translander/TranslanderApp.kt | 41 ++++++++++++++++++- .../translander/service/FloatingMicService.kt | 29 ++++++++++++- .../transcribe/AudioMonitorService.kt | 29 ++++++++++++- app/src/main/res/values-bg/strings.xml | 4 ++ app/src/main/res/values-cs/strings.xml | 4 ++ app/src/main/res/values-da/strings.xml | 4 ++ app/src/main/res/values-de/strings.xml | 4 ++ app/src/main/res/values-el/strings.xml | 4 ++ app/src/main/res/values-es/strings.xml | 4 ++ app/src/main/res/values-et/strings.xml | 4 ++ app/src/main/res/values-fi/strings.xml | 4 ++ app/src/main/res/values-fr/strings.xml | 4 ++ app/src/main/res/values-hr/strings.xml | 4 ++ app/src/main/res/values-hu/strings.xml | 4 ++ app/src/main/res/values-it/strings.xml | 4 ++ app/src/main/res/values-lt/strings.xml | 4 ++ app/src/main/res/values-lv/strings.xml | 4 ++ app/src/main/res/values-mt/strings.xml | 4 ++ app/src/main/res/values-nl/strings.xml | 4 ++ app/src/main/res/values-pl/strings.xml | 4 ++ app/src/main/res/values-pt/strings.xml | 4 ++ app/src/main/res/values-ro/strings.xml | 4 ++ app/src/main/res/values-sk/strings.xml | 4 ++ app/src/main/res/values-sl/strings.xml | 4 ++ app/src/main/res/values-sv/strings.xml | 4 ++ app/src/main/res/values-uk/strings.xml | 4 ++ app/src/main/res/values/strings.xml | 4 ++ .../metadata/android/en-US/changelogs/1.txt | 8 ---- 30 files changed, 205 insertions(+), 14 deletions(-) delete mode 100644 fastlane/metadata/android/en-US/changelogs/1.txt diff --git a/CLAUDE.md b/CLAUDE.md index dbc4580..f2b71be 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -86,9 +86,20 @@ The NDK version must match between the fdroiddata YAML and the local SDK. `ANDRO | fdroid build skips silently | APK already exists in `unsigned/` | Remove existing APK and src tarball | | `Environment variable {env: serverwebroot}` | Missing fdroid config (warning, not fatal) | Ignore — only needed for `fdroid publish` | | Lint `MissingTranslation` | New string added to `values/strings.xml` without translations | Add to all `values-{locale}/strings.xml` files | + +**Translations workflow:** When adding new strings, defer translations until the feature is complete and stable. This avoids repeatedly updating 23 locale files during iterative development. Add all translations in one batch before the release. | Lint `ExtraTranslation` | String in translation file not in default `strings.xml` | Remove from translation files | | XML apostrophe crash | Unescaped `'` in strings.xml | Use `\'` — e.g., `there\'s` not `there's` | +## Release Checklist + +When bumping a version, update all of these: +1. `app/build.gradle.kts` — `versionCode` and `versionName` +2. `CHANGELOG.md` — Add new version entry with date +3. `fastlane/metadata/android/en-US/changelogs/{versionCode}.txt` — Short changelog for F-Droid/store +4. `github-release.md` — Release notes for GitHub releases +5. Tag the release: `git tag v{versionName}` + ## Architecture ### Core Components diff --git a/README.md b/README.md index 4b2cd0a..786a24b 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ A fully offline voice typing app for Android. Speak into your phone and text app - Uses [Parakeet TDT v3](https://huggingface.co/csukuangfj/sherpa-onnx-nemo-parakeet-tdt-0.6b-v3-int8) neural model (~600MB) running locally via [sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx) - Supports 25 languages with auto-detection - No internet connection needed after model download +- **Import from folder** — load model files from local storage instead of downloading ### Works in Any App - Uses Android Accessibility API to inject text directly into any focused text field @@ -148,7 +149,7 @@ app/src/main/java/com/translander/ ├── asr/ # Speech recognition │ ├── AudioRecorder.kt # 16kHz PCM recording │ ├── DictionaryManager.kt # Word correction rules -│ ├── ModelManager.kt # Model download from HuggingFace +│ ├── ModelManager.kt # Model download and local import │ ├── ParakeetRecognizer.kt # ONNX inference wrapper │ └── RecognizerManager.kt # Shared recognizer singleton ├── ime/ @@ -195,7 +196,7 @@ This app is provided as-is for personal use. Speech recognition accuracy depends **Model Attribution:** Speech recognition uses [NVIDIA Parakeet TDT](https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3), licensed under [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/). ONNX conversion by [csukuangfj/sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx). -**Privacy:** All speech processing happens locally on your device. No audio data is ever sent to any server. After the [speech model](https://huggingface.co/csukuangfj/sherpa-onnx-nemo-parakeet-tdt-0.6b-v3-int8) is downloaded, you can revoke the Network permission in Android Settings to ensure the app can never access the internet. +**Privacy:** All speech processing happens locally on your device. No audio data is ever sent to any server. You can [download the model](https://huggingface.co/csukuangfj/sherpa-onnx-nemo-parakeet-tdt-0.6b-v3-int8) directly or import it from a local folder, then revoke the Network permission in Android Settings to ensure the app can never access the internet. --- diff --git a/app/src/main/java/com/translander/TranslanderApp.kt b/app/src/main/java/com/translander/TranslanderApp.kt index 977aa46..b3dfbfa 100644 --- a/app/src/main/java/com/translander/TranslanderApp.kt +++ b/app/src/main/java/com/translander/TranslanderApp.kt @@ -3,8 +3,12 @@ package com.translander import android.app.Application import android.app.NotificationChannel import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Intent import android.os.Build import android.util.Log +import androidx.core.app.NotificationCompat +import com.translander.settings.SettingsActivity import com.translander.asr.DictionaryManager import com.translander.asr.ModelManager import com.translander.asr.RecognizerManager @@ -64,6 +68,8 @@ class TranslanderApp : Application() { private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val notificationManager = getSystemService(NotificationManager::class.java) + val channel = NotificationChannel( NOTIFICATION_CHANNEL_ID, "Voice Transcribe Service", @@ -72,16 +78,47 @@ class TranslanderApp : Application() { description = "Shows when voice transcription service is active" setShowBadge(false) } - - val notificationManager = getSystemService(NotificationManager::class.java) notificationManager.createNotificationChannel(channel) + + val alertChannel = NotificationChannel( + SERVICE_ALERT_CHANNEL_ID, + getString(R.string.service_alert_channel_name), + NotificationManager.IMPORTANCE_DEFAULT + ).apply { + description = getString(R.string.service_alert_channel_description) + } + notificationManager.createNotificationChannel(alertChannel) + } + } + + fun showServiceStartNotification(messageResId: Int) { + val intent = Intent(this, SettingsActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP } + val pendingIntent = PendingIntent.getActivity( + this, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + val notification = NotificationCompat.Builder(this, SERVICE_ALERT_CHANNEL_ID) + .setSmallIcon(R.drawable.ic_mic) + .setContentTitle(getString(R.string.app_name)) + .setContentText(getString(messageResId)) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setContentIntent(pendingIntent) + .setAutoCancel(true) + .build() + + val notificationManager = getSystemService(NotificationManager::class.java) + notificationManager.notify(SERVICE_ALERT_NOTIFICATION_ID, notification) } companion object { private const val TAG = "TranslanderApp" const val NOTIFICATION_CHANNEL_ID = "voice_transcribe_service" + const val SERVICE_ALERT_CHANNEL_ID = "service_alert" const val NOTIFICATION_ID = 1001 + const val SERVICE_ALERT_NOTIFICATION_ID = 1002 lateinit var instance: TranslanderApp private set diff --git a/app/src/main/java/com/translander/service/FloatingMicService.kt b/app/src/main/java/com/translander/service/FloatingMicService.kt index cc39a78..eef0f01 100644 --- a/app/src/main/java/com/translander/service/FloatingMicService.kt +++ b/app/src/main/java/com/translander/service/FloatingMicService.kt @@ -91,7 +91,30 @@ class FloatingMicService : Service() { } } - startForeground(TranslanderApp.NOTIFICATION_ID, createNotification()) + try { + startForeground(TranslanderApp.NOTIFICATION_ID, createNotification()) + // Dismiss any failure notification from previous boot attempt + getSystemService(android.app.NotificationManager::class.java) + ?.cancel(TranslanderApp.SERVICE_ALERT_NOTIFICATION_ID) + } catch (e: android.app.ForegroundServiceStartNotAllowedException) { + // Android 14+ blocks microphone FGS from BOOT_COMPLETED or background + Log.w(TAG, "Cannot start foreground service from boot/background", e) + showFailureNotification() + stopSelf() + return START_NOT_STICKY + } catch (e: SecurityException) { + // Missing RECORD_AUDIO permission for microphone FGS type + Log.w(TAG, "Missing permission for foreground service", e) + showFailureNotification() + stopSelf() + return START_NOT_STICKY + } catch (e: IllegalStateException) { + // Race condition: app went to background before startForeground completed + Log.w(TAG, "Cannot start foreground service, app in background", e) + showFailureNotification() + stopSelf() + return START_NOT_STICKY + } return START_NOT_STICKY // Don't auto-restart } @@ -322,6 +345,10 @@ class FloatingMicService : Service() { ) } + private fun showFailureNotification() { + TranslanderApp.instance.showServiceStartNotification(R.string.service_start_floating_mic) + } + private fun createNotification(): Notification { val stopIntent = Intent(this, FloatingMicService::class.java).apply { action = ACTION_STOP diff --git a/app/src/main/java/com/translander/transcribe/AudioMonitorService.kt b/app/src/main/java/com/translander/transcribe/AudioMonitorService.kt index d1808cf..77d117c 100644 --- a/app/src/main/java/com/translander/transcribe/AudioMonitorService.kt +++ b/app/src/main/java/com/translander/transcribe/AudioMonitorService.kt @@ -62,7 +62,30 @@ class AudioMonitorService : Service() { return START_NOT_STICKY } - startForeground(SERVICE_NOTIFICATION_ID, createServiceNotification()) + try { + startForeground(SERVICE_NOTIFICATION_ID, createServiceNotification()) + // Dismiss any failure notification from previous boot attempt + getSystemService(android.app.NotificationManager::class.java) + ?.cancel(TranslanderApp.SERVICE_ALERT_NOTIFICATION_ID) + } catch (e: android.app.ForegroundServiceStartNotAllowedException) { + // Android 14+ blocks dataSync FGS from BOOT_COMPLETED or background + Log.w(TAG, "Cannot start foreground service from boot/background", e) + showFailureNotification() + stopSelf() + return START_NOT_STICKY + } catch (e: SecurityException) { + // Missing permission for FGS type + Log.w(TAG, "Missing permission for foreground service", e) + showFailureNotification() + stopSelf() + return START_NOT_STICKY + } catch (e: IllegalStateException) { + // Race condition: app went to background before startForeground completed + Log.w(TAG, "Cannot start foreground service, app in background", e) + showFailureNotification() + stopSelf() + return START_NOT_STICKY + } startMonitoring() return START_STICKY @@ -103,6 +126,10 @@ class AudioMonitorService : Service() { } } + private fun showFailureNotification() { + TranslanderApp.instance.showServiceStartNotification(R.string.service_start_folder_monitor) + } + private fun createServiceNotification(): Notification { val stopIntent = Intent(this, AudioMonitorService::class.java).apply { action = ACTION_STOP diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 0a06e21..8e7b418 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -5,6 +5,10 @@ TranSlander е активен Докоснете плаващия микрофон за транскрибиране на реч + Сигнали за услуги + Известия при неуспешно стартиране на услуги + Натиснете, за да стартирате плаващия микрофон + Натиснете, за да стартирате наблюдението на папки TranSlander Текстов вход Позволява на TranSlander да вмъква транскрибиран текст директно в текстови полета. Активирайте за безпроблемно гласово въвеждане на текст. diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index ef61252..161e94f 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -5,6 +5,10 @@ TranSlander je aktivní Klepněte na plovoucí mikrofon pro přepis řeči + Upozornění služeb + Oznámení při selhání spuštění služeb + Klepněte pro spuštění plovoucího mikrofonu + Klepněte pro spuštění monitoru složek TranSlander Textový vstup Umožňuje TranSlander vkládat přepsaný text přímo do textových polí. Povolte pro plynulý hlasový vstup. diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index ad2e22d..c904621 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -3,6 +3,10 @@ Indstillinger TranSlander aktiv Tryk på den flydende mikrofon for at transskribere tale + Serviceadvarsler + Notifikationer når tjenester ikke kan starte + Tryk for at starte flydende mikrofon + Tryk for at starte mappeovervågning TranSlander tekstinput Giver TranSlander mulighed for at indsætte transskriberet tekst direkte i tekstfelter. Aktivér dette for problemfri tale-til-tekst-input. Talemodel diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 96a17ec..2cccf4e 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -5,6 +5,10 @@ TranSlander aktiv Auf das schwebende Mikrofon tippen, um Sprache zu transkribieren + Dienstwarnungen + Benachrichtigungen, wenn Dienste nicht starten können + Tippen, um schwebendes Mikrofon zu starten + Tippen, um Ordnerüberwachung zu starten TranSlander Texteingabe Ermöglicht TranSlander, transkribierten Text direkt in Textfelder einzufügen. Für nahtlose Spracheingabe aktivieren. diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 9ba531f..64873b5 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -3,6 +3,10 @@ Ρυθμίσεις Το TranSlander είναι ενεργό Πατήστε το αιωρούμενο μικρόφωνο για μεταγραφή ομιλίας + Ειδοποιήσεις υπηρεσιών + Ειδοποιήσεις όταν οι υπηρεσίες αποτυγχάνουν να ξεκινήσουν + Πατήστε για εκκίνηση αιωρούμενου μικροφώνου + Πατήστε για εκκίνηση παρακολούθησης φακέλων Εισαγωγή κειμένου TranSlander Επιτρέπει στο TranSlander να εισάγει μεταγεγραμμένο κείμενο απευθείας σε πεδία κειμένου. Ενεργοποιήστε το για απρόσκοπτη εισαγωγή ομιλίας-σε-κείμενο. Μοντέλο ομιλίας diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 166b375..01615e9 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -5,6 +5,10 @@ TranSlander activo Toca el micrófono flotante para transcribir voz + Alertas de servicios + Notificaciones cuando los servicios no pueden iniciarse + Toca para iniciar el micrófono flotante + Toca para iniciar el monitor de carpetas Entrada de texto TranSlander Permite a TranSlander insertar texto transcrito directamente en campos de texto. Actívalo para una entrada de voz fluida. diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index dd8b618..fa4f232 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -3,6 +3,10 @@ Seaded TranSlander aktiivne Puuduta hõljuvat mikrofoni kõne transkribeerimiseks + Teenuse hoiatused + Teavitused, kui teenused ei käivitu + Puuduta hõljuva mikrofoni käivitamiseks + Puuduta kaustaseire käivitamiseks TranSlander tekstisisestus Lubab TranSlander\'il sisestada transkribeeritud teksti otse tekstiväljadele. Luba see sujuvaks kõnest tekstiks sisestuseks. Kõnemudel diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index c24ff85..b397a3e 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -3,6 +3,10 @@ Asetukset TranSlander aktiivinen Napauta kelluvaa mikrofonia puheentunnistusta varten + Palveluhälytykset + Ilmoitukset kun palvelut eivät käynnisty + Napauta käynnistääksesi kelluvan mikrofonin + Napauta käynnistääksesi kansioseurannan TranSlander-tekstinsyöttö Sallii TranSlanderin lisätä tunnistetun tekstin suoraan tekstikenttiin. Ota käyttöön saumattomaan puheesta tekstiksi -syöttöön. Puhemalli diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index a6b2cad..365b5e8 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -5,6 +5,10 @@ TranSlander actif Touche le micro flottant pour transcrire la parole + Alertes de service + Notifications lorsque les services ne démarrent pas + Touche pour démarrer le micro flottant + Touche pour démarrer la surveillance des dossiers Saisie de texte TranSlander Permet à TranSlander d\'insérer du texte transcrit directement dans les champs de saisie. Active cette option pour une saisie vocale fluide. diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index aa5f242..e3b0efb 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -5,6 +5,10 @@ TranSlander je aktivan Dodirnite plutajući mikrofon za transkripciju govora + Upozorenja usluga + Obavijesti kada se usluge ne mogu pokrenuti + Dodirnite za pokretanje plutajućeg mikrofona + Dodirnite za pokretanje praćenja mapa TranSlander Unos teksta Omogućuje TranSlander umetanje transkribiranog teksta izravno u tekstualna polja. Omogućite za besprijekoran glasovni unos teksta. diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index b8a2fb0..3c7be4d 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -5,6 +5,10 @@ TranSlander aktív Koppintson a lebegő mikrofonra a beszéd átírásához + Szolgáltatás figyelmeztetések + Értesítések, ha a szolgáltatások nem indulnak el + Koppintson a lebegő mikrofon indításához + Koppintson a mappafigyelő indításához TranSlander szövegbevitel Lehetővé teszi a TranSlander számára, hogy az átírt szöveget közvetlenül a szövegmezőkbe illessze be. Engedélyezze a zökkenőmentes hangalapú szövegbevitelhez. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index c4ad4ef..afdcd9b 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -5,6 +5,10 @@ TranSlander attivo Tocca il microfono flottante per trascrivere il parlato + Avvisi di servizio + Notifiche quando i servizi non riescono ad avviarsi + Tocca per avviare il microfono flottante + Tocca per avviare il monitoraggio cartelle Immissione testo TranSlander Consente a TranSlander di inserire testo trascritto direttamente nei campi di testo. Attivalo per un\'immissione vocale fluida. diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index aafe42d..b73c81b 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -3,6 +3,10 @@ Nustatymai TranSlander aktyvus Bakstelėkite slankųjį mikrofoną, kad transkribuotumėte kalbą + Paslaugų įspėjimai + Pranešimai, kai paslaugos nepavyksta paleisti + Bakstelėkite, kad paleistumėte slankųjį mikrofoną + Bakstelėkite, kad paleistumėte aplankų stebėjimą TranSlander teksto įvestis Leidžia TranSlander įterpti transkribuotą tekstą tiesiai į teksto laukus. Įjunkite tai sklandžiai balso-į-tekstą įvesčiai. Kalbos modelis diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index f55dd8d..a8b00ca 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -3,6 +3,10 @@ Iestatījumi TranSlander aktīvs Pieskarieties peldošajam mikrofonam, lai transkribētu runu + Pakalpojumu brīdinājumi + Paziņojumi, kad pakalpojumi neizdodas startēt + Pieskarieties, lai palaistu peldošo mikrofonu + Pieskarieties, lai palaistu mapju uzraudzību TranSlander teksta ievade Ļauj TranSlander ievietot transkribētu tekstu tieši teksta laukos. Iespējojiet šo vienmērīgai runas-teksta ievadei. Runas modelis diff --git a/app/src/main/res/values-mt/strings.xml b/app/src/main/res/values-mt/strings.xml index e22b381..5632f71 100644 --- a/app/src/main/res/values-mt/strings.xml +++ b/app/src/main/res/values-mt/strings.xml @@ -3,6 +3,10 @@ Settings TranSlander Attiv Tektek il-mikrofonu li jżomm biex tittraskrivi d-diskors + Twissijiet tas-Servizz + Notifiki meta s-servizzi ma jirnexxilhomx jibdew + Tektek biex tibda l-mikrofonu li jżomm + Tektek biex tibda l-monitoraġġ tal-folders Dħul ta\' test TranSlander Jippermetti lil TranSlander idaħħal test ittraskkritt direttament f\'oqsma tat-test. Attiva dan għal dħul bla xkiel ta\' vuċi-għal-test. Mudell tad-Diskors diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index c753eda..e9ad1a6 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -3,6 +3,10 @@ Instellingen TranSlander actief Tik op de zwevende microfoon om spraak te transcriberen + Servicewaarschuwingen + Meldingen wanneer services niet kunnen starten + Tik om zwevende microfoon te starten + Tik om mapbewaking te starten TranSlander tekstinvoer Hiermee kan TranSlander getranscribeerde tekst rechtstreeks in tekstvelden invoegen. Schakel dit in voor naadloze spraak-naar-tekst invoer. Spraakmodel diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 25fbe6f..e5bc5e8 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -3,6 +3,10 @@ Ustawienia TranSlander aktywny Dotknij pływającego mikrofonu, aby transkrybować mowę + Ostrzeżenia usług + Powiadomienia, gdy usługi nie mogą się uruchomić + Dotknij, aby uruchomić pływający mikrofon + Dotknij, aby uruchomić monitor folderów TranSlander wprowadzanie tekstu Umożliwia TranSlander wstawianie transkrybowanego tekstu bezpośrednio do pól tekstowych. Włącz tę opcję, aby płynnie zamieniać mowę na tekst. Model mowy diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 3e8932b..45a420e 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -3,6 +3,10 @@ Definições TranSlander ativo Toque no microfone flutuante para transcrever voz + Alertas de serviço + Notificações quando os serviços não conseguem iniciar + Toque para iniciar o microfone flutuante + Toque para iniciar o monitor de pastas TranSlander introdução de texto Permite que o TranSlander insira texto transcrito diretamente em campos de texto. Ative esta opção para uma introdução de voz para texto sem interrupções. Modelo de voz diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index b31aa8c..bf0486e 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -3,6 +3,10 @@ Setări TranSlander activ Atinge microfonul plutitor pentru a transcrie vorbirea + Alerte de serviciu + Notificări când serviciile nu reușesc să pornească + Atinge pentru a porni microfonul plutitor + Atinge pentru a porni monitorizarea dosarelor TranSlander introducere text Permite TranSlander să insereze textul transcris direct în câmpurile de text. Activează această opțiune pentru introducere vocală fără întreruperi. Model de vorbire diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 0231112..a770199 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -5,6 +5,10 @@ TranSlander je aktívny Klepnite na plávajúci mikrofón pre prepis reči + Upozornenia služieb + Oznámenia pri zlyhaní spustenia služieb + Klepnite pre spustenie plávajúceho mikrofónu + Klepnite pre spustenie monitorovania priečinkov TranSlander Textový vstup Umožňuje TranSlander vkladať prepísaný text priamo do textových polí. Povoľte pre plynulý hlasový vstup. diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index 4fbf352..2f7d163 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -5,6 +5,10 @@ TranSlander je aktiven Tapnite plavajoči mikrofon za prepis govora + Opozorila storitev + Obvestila, ko se storitve ne zaženejo + Tapnite za zagon plavajočega mikrofona + Tapnite za zagon nadzora map TranSlander Vnos besedila Omogoča TranSlander vstavljanje prepisanega besedila neposredno v besedilna polja. Omogočite za nemoten glasovni vnos besedila. diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index e0dfd7d..4ad3f33 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -3,6 +3,10 @@ Inställningar TranSlander aktiv Tryck på den flytande mikrofonen för att transkribera tal + Tjänstvarningar + Aviseringar när tjänster inte kan starta + Tryck för att starta flytande mikrofon + Tryck för att starta mappövervakning TranSlander textinmatning Låter TranSlander infoga transkriberad text direkt i textfält. Aktivera detta för sömlös röst-till-text-inmatning. Talmodell diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index b3245f6..6e5f264 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -3,6 +3,10 @@ Налаштування TranSlander активний Натисніть плаваючий мікрофон для транскрибування мовлення + Сповіщення служб + Сповіщення, коли служби не можуть запуститися + Натисніть, щоб запустити плаваючий мікрофон + Натисніть, щоб запустити моніторинг тек Введення тексту TranSlander Дозволяє TranSlander вставляти транскрибований текст безпосередньо в текстові поля. Увімкніть для безперебійного введення мовлення-в-текст. Модель мовлення diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5586e6b..d866c9d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,6 +6,10 @@ TranSlander Active Tap the floating mic to transcribe speech + Service Alerts + Notifications when services need to be started + Tap to start floating mic + Tap to start folder monitor TranSlander Text Input Allows TranSlander to insert transcribed text directly into text fields. Enable this for seamless voice-to-text input. diff --git a/fastlane/metadata/android/en-US/changelogs/1.txt b/fastlane/metadata/android/en-US/changelogs/1.txt deleted file mode 100644 index 6d202eb..0000000 --- a/fastlane/metadata/android/en-US/changelogs/1.txt +++ /dev/null @@ -1,8 +0,0 @@ -Initial release: -• Offline speech recognition with Parakeet TDT v3 model -• Floating mic button and accessibility button -• Voice message transcription (share audio from any app) -• Word corrections for fixing recognition errors -• Keyboard integration for HeliBoard and others -• 25 languages with auto-detection -• Material 3 UI with dark/light themes From e202438f30bc5c17e70b323080374170542bb90d Mon Sep 17 00:00:00 2001 From: hatsch Date: Tue, 3 Feb 2026 08:52:04 +0100 Subject: [PATCH 2/2] Request notification permission when enabling services On Android 13+, POST_NOTIFICATIONS is a runtime permission. Request it when user enables floating mic or folder monitor to ensure the "tap to start" notification can be shown after boot. Co-Authored-By: Claude Opus 4.5 --- .../translander/settings/SettingsActivity.kt | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/translander/settings/SettingsActivity.kt b/app/src/main/java/com/translander/settings/SettingsActivity.kt index 1951d43..5df64ab 100644 --- a/app/src/main/java/com/translander/settings/SettingsActivity.kt +++ b/app/src/main/java/com/translander/settings/SettingsActivity.kt @@ -141,8 +141,12 @@ class SettingsActivity : ComponentActivity() { hasAudioFilesPermission = { hasAudioFilesPermission() }, onOpenAccessibilitySettings = { openAccessibilitySettings() }, onOpenAppSettings = { openAppSettings() }, - onStartService = { startFloatingService() }, + onStartService = { + requestNotificationPermissionIfNeeded() + startFloatingService() + }, onStopService = { stopFloatingService() }, + onStartAudioMonitor = { requestNotificationPermissionIfNeeded() }, onRestartService = { restartFloatingService() }, onPickFolder = { callback -> openFolderPicker(callback) }, onPickModelFolder = { callback -> @@ -205,6 +209,20 @@ class SettingsActivity : ComponentActivity() { } } + private fun hasNotificationPermission(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED + } else { + true // Not needed before Android 13 + } + } + + private fun requestNotificationPermissionIfNeeded() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !hasNotificationPermission()) { + requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } + } + private fun requestOverlayPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val intent = Intent( @@ -294,6 +312,7 @@ fun SettingsScreen( onStartService: () -> Unit, onStopService: () -> Unit, onRestartService: () -> Unit, + onStartAudioMonitor: () -> Unit, onPickFolder: ((String) -> Unit) -> Unit, onPickModelFolder: ((Uri) -> Unit) -> Unit, isVoiceImeEnabled: () -> Boolean, @@ -589,6 +608,7 @@ fun SettingsScreen( enabled = hasAudioPermission.value, onCheckedChange = { enabled -> scope.launch { + if (enabled) onStartAudioMonitor() settingsRepository.setAudioMonitorEnabled(enabled) transcribeManager.setAudioMonitorEnabled(enabled) }