Compare commits

44 Commits
main ... dev

Author SHA1 Message Date
798190cb45 Merge remote-tracking branch 'origin/dev' into dev 2026-03-13 15:49:32 +01:00
2ea34ff41c jarvis-ai 2026-03-13 15:48:54 +01:00
277e44ebf6 app/src/main/java/com/example/jarvis_stts/JarvisService.kt aktualisiert 2026-03-13 14:44:56 +00:00
b820c9a7be app/src/main/java/com/example/jarvis_stts/MainActivity.kt aktualisiert 2026-03-13 14:31:24 +00:00
fe599f72f5 app/src/main/res/layout/activity_main.xml aktualisiert 2026-03-13 13:55:30 +00:00
d21e4f37f1 app/src/main/java/com/example/jarvis_stts/MainActivity.kt aktualisiert 2026-03-13 13:40:01 +00:00
dca5e0f00f app/src/main/java/com/example/jarvis_stts/JarvisService.kt aktualisiert 2026-03-13 13:39:15 +00:00
b2e9866b78 app/src/main/java/com/example/jarvis_stts/JarvisService.kt aktualisiert 2026-03-13 13:24:08 +00:00
570a3abbe9 app/src/main/java/com/example/jarvis_stts/JarvisService.kt aktualisiert 2026-03-13 13:06:05 +00:00
1f78f5cfcc Wake-Word-Erkennung verbessert 2026-03-13 14:03:58 +01:00
0fc4eae291 rename JarvisService 2026-03-13 12:04:40 +01:00
cbb7e14c81 app/src/main/java/com/example/jarvis_stts/JarviceService.kt aktualisiert 2026-03-13 08:50:35 +00:00
62e1bfbe16 .gitignore hinzugefügt 2026-03-13 08:24:12 +00:00
0e1347ddfd .DS_Store gelöscht 2026-03-13 08:18:50 +00:00
b41ff1385c app/.DS_Store gelöscht 2026-03-13 08:18:42 +00:00
13637f8a0f app/src/.DS_Store gelöscht 2026-03-13 08:18:35 +00:00
97bd45071a app/src/main/.DS_Store gelöscht 2026-03-13 08:18:10 +00:00
0c250e4734 app/src/main/java/.DS_Store gelöscht 2026-03-13 08:17:23 +00:00
bbc90d64b4 app/src/main/java/com/.DS_Store gelöscht 2026-03-13 08:17:12 +00:00
b392f7d58f app/src/main/java/com/example/.DS_Store gelöscht 2026-03-13 08:16:42 +00:00
d7d3f43dc3 app/src/main/java/com/example/jarvis_stts/JarviceService.kt aktualisiert 2026-03-12 17:04:31 +00:00
0c9766f7cc app/src/main/java/com/example/jarvis_stts/JarviceService.kt aktualisiert 2026-03-12 16:35:57 +00:00
ad46c99993 app/src/main/java/com/example/jarvis_stts/JarviceService.kt aktualisiert 2026-03-12 16:15:57 +00:00
2557039f84 jetzt als Hintergrund Service mit Wake-Word-Aktivierung 2026-03-12 13:30:50 +01:00
4c1407a61c app/src/main/java/com/example/jarvis_stts/MainActivity.kt aktualisiert 2026-03-11 16:44:44 +00:00
9bbd0e9f84 app/src/main/java/com/example/jarvis_stts/MainActivity.kt aktualisiert 2026-03-11 16:33:42 +00:00
7f7ffa6dc2 app/src/main/java/com/example/jarvis_stts/MainActivity.kt aktualisiert 2026-03-11 16:32:35 +00:00
e86a2eb48c app/src/main/java/com/example/jarvis_stts/MainActivity.kt aktualisiert 2026-03-11 16:26:02 +00:00
5a5709de61 app/build.gradle.kts aktualisiert 2026-03-11 16:09:23 +00:00
febfa7946f app/build.gradle.kts aktualisiert 2026-03-11 16:04:36 +00:00
1f3468ad4f app/build.gradle.kts aktualisiert 2026-03-11 16:01:12 +00:00
e08de09b83 app/build.gradle.kts aktualisiert 2026-03-11 15:58:14 +00:00
1dc98861f9 app/build.gradle.kts aktualisiert 2026-03-11 15:43:03 +00:00
d10462de3f app/build.gradle.kts aktualisiert 2026-03-11 15:42:00 +00:00
fd012f767c app/src/main/java/com/example/jarvis_stts/MainActivity.kt aktualisiert 2026-03-11 15:27:14 +00:00
209571a5d1 app/src/main/java/com/example/jarvis_stts/JarvisService.kt aktualisiert 2026-03-11 15:26:04 +00:00
be504a6135 app/src/main/java/com/example/jarvis_stts/MainActivity.kt aktualisiert 2026-03-11 15:15:27 +00:00
221e926ca2 app/src/main/java/com/example/jarvis_stts/MainActivity.kt aktualisiert 2026-03-11 14:54:16 +00:00
54aa9686a5 app/src/main/java/com/example/jarvis_stts/MainActivity.kt aktualisiert 2026-03-11 14:41:09 +00:00
4f240c88a1 app/src/main/java/com/example/jarvis_stts/MainActivity.kt aktualisiert 2026-03-11 14:37:16 +00:00
26c40dfadf app/src/main/java/com/example/jarvis_stts/JarvisService.kt aktualisiert 2026-03-11 14:31:09 +00:00
52c690c4a3 app/src/main/java/com/example/jarvis_stts/MainActivity.kt aktualisiert 2026-03-11 14:13:37 +00:00
b9721df7ab app/src/main/AndroidManifest.xml aktualisiert 2026-03-11 14:10:15 +00:00
20fafd40dd app/src/main/java/com/example/jarvis_stts/JarvisService.kt hinzugefügt 2026-03-11 14:08:40 +00:00
22 changed files with 979 additions and 81 deletions

View File

@@ -16,8 +16,10 @@ android {
targetSdk = 36
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
ndk {
abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64"))
}
}
buildTypes {
@@ -36,10 +38,13 @@ android {
}
dependencies {
implementation("net.java.dev.jna:jna:5.13.0@aar")
//implementation("org.vosk:vosk-android:0.3.45")
// OkHttp für Netzwerkanfragen an Telegram
implementation("com.alphacephei:vosk-android:0.3.45")
implementation("com.alphacephei:vosk-android:0.3.45"){
exclude(group = "net.java.dev.jna", module = "jna")
}
implementation("com.squareup.okhttp3:okhttp:4.12.0")
//implementation ("ai.picovoice:porcupine-android:3.0.2")
// Coroutinen für Hintergrund-Prozesse (Polling)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
implementation(libs.androidx.core.ktx)

View File

@@ -1,31 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
package="com.example.jarvis_stts">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application
android:usesCleartextTraffic="true"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Jarvisstts">
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="true">
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<service
android:name=".JarvisService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="microphone" />
</application>
</manifest>

View File

@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. this License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@@ -0,0 +1,12 @@
German model for mobile Vosk applications
Copyright © 2020 Alpha Cephei Inc
%WER 28.63 [ 3442 / 12023, 497 ins, 919 del, 2026 sub ] exp/chain_a/tdnn/decode_test-podcast-wb/wer_10_0.0
%WER 30.67 [ 3688 / 12023, 520 ins, 954 del, 2214 sub ] exp/chain_a/tdnn/decode_test-podcast-wb_l/wer_10_0.0
%WER 26.68 [ 3208 / 12023, 464 ins, 910 del, 1834 sub ] exp/chain_a/tdnn/decode_test-podcast-wb_rescore/wer_10_0.0
%WER 11.04 [ 7683 / 69600, 1592 ins, 925 del, 5166 sub ] exp/chain_a/tdnn/decode_test-wb/wer_10_0.5
%WER 13.75 [ 9567 / 69600, 1896 ins, 1126 del, 6545 sub ] exp/chain_a/tdnn/decode_test-wb_l/wer_10_0.5
%WER 9.02 [ 6279 / 69600, 1241 ins, 879 del, 4159 sub ] exp/chain_a/tdnn/decode_test-wb_rescore/wer_11_0.5
Time taken 45.1052s: real-time factor assuming 100 frames/sec is 0.115468

Binary file not shown.

View File

@@ -0,0 +1,6 @@
--use-energy=false
--sample-frequency=16000
--num-mel-bins=30
--num-ceps=30
--low-freq=100
--high-freq=7600

View File

@@ -0,0 +1,10 @@
--min-active=200
--max-active=3000
--beam=10.0
--lattice-beam=2.0
--acoustic-scale=1.0
--frame-subsampling-factor=3
--endpoint.silence-phones=1:2:3:4:5:6:7:8:9:10
--endpoint.rule2.min-trailing-silence=0.5
--endpoint.rule3.min-trailing-silence=1.0
--endpoint.rule4.min-trailing-silence=2.0

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,7 @@
14297
14298
14299
14300
14301
14302
14303

View File

@@ -0,0 +1,410 @@
1 nonword
2 begin
3 end
4 internal
5 singleton
6 nonword
7 begin
8 end
9 internal
10 singleton
11 begin
12 end
13 internal
14 singleton
15 begin
16 end
17 internal
18 singleton
19 begin
20 end
21 internal
22 singleton
23 begin
24 end
25 internal
26 singleton
27 begin
28 end
29 internal
30 singleton
31 begin
32 end
33 internal
34 singleton
35 begin
36 end
37 internal
38 singleton
39 begin
40 end
41 internal
42 singleton
43 begin
44 end
45 internal
46 singleton
47 begin
48 end
49 internal
50 singleton
51 begin
52 end
53 internal
54 singleton
55 begin
56 end
57 internal
58 singleton
59 begin
60 end
61 internal
62 singleton
63 begin
64 end
65 internal
66 singleton
67 begin
68 end
69 internal
70 singleton
71 begin
72 end
73 internal
74 singleton
75 begin
76 end
77 internal
78 singleton
79 begin
80 end
81 internal
82 singleton
83 begin
84 end
85 internal
86 singleton
87 begin
88 end
89 internal
90 singleton
91 begin
92 end
93 internal
94 singleton
95 begin
96 end
97 internal
98 singleton
99 begin
100 end
101 internal
102 singleton
103 begin
104 end
105 internal
106 singleton
107 begin
108 end
109 internal
110 singleton
111 begin
112 end
113 internal
114 singleton
115 begin
116 end
117 internal
118 singleton
119 begin
120 end
121 internal
122 singleton
123 begin
124 end
125 internal
126 singleton
127 begin
128 end
129 internal
130 singleton
131 begin
132 end
133 internal
134 singleton
135 begin
136 end
137 internal
138 singleton
139 begin
140 end
141 internal
142 singleton
143 begin
144 end
145 internal
146 singleton
147 begin
148 end
149 internal
150 singleton
151 begin
152 end
153 internal
154 singleton
155 begin
156 end
157 internal
158 singleton
159 begin
160 end
161 internal
162 singleton
163 begin
164 end
165 internal
166 singleton
167 begin
168 end
169 internal
170 singleton
171 begin
172 end
173 internal
174 singleton
175 begin
176 end
177 internal
178 singleton
179 begin
180 end
181 internal
182 singleton
183 begin
184 end
185 internal
186 singleton
187 begin
188 end
189 internal
190 singleton
191 begin
192 end
193 internal
194 singleton
195 begin
196 end
197 internal
198 singleton
199 begin
200 end
201 internal
202 singleton
203 begin
204 end
205 internal
206 singleton
207 begin
208 end
209 internal
210 singleton
211 begin
212 end
213 internal
214 singleton
215 begin
216 end
217 internal
218 singleton
219 begin
220 end
221 internal
222 singleton
223 begin
224 end
225 internal
226 singleton
227 begin
228 end
229 internal
230 singleton
231 begin
232 end
233 internal
234 singleton
235 begin
236 end
237 internal
238 singleton
239 begin
240 end
241 internal
242 singleton
243 begin
244 end
245 internal
246 singleton
247 begin
248 end
249 internal
250 singleton
251 begin
252 end
253 internal
254 singleton
255 begin
256 end
257 internal
258 singleton
259 begin
260 end
261 internal
262 singleton
263 begin
264 end
265 internal
266 singleton
267 begin
268 end
269 internal
270 singleton
271 begin
272 end
273 internal
274 singleton
275 begin
276 end
277 internal
278 singleton
279 begin
280 end
281 internal
282 singleton
283 begin
284 end
285 internal
286 singleton
287 begin
288 end
289 internal
290 singleton
291 begin
292 end
293 internal
294 singleton
295 begin
296 end
297 internal
298 singleton
299 begin
300 end
301 internal
302 singleton
303 begin
304 end
305 internal
306 singleton
307 begin
308 end
309 internal
310 singleton
311 begin
312 end
313 internal
314 singleton
315 begin
316 end
317 internal
318 singleton
319 begin
320 end
321 internal
322 singleton
323 begin
324 end
325 internal
326 singleton
327 begin
328 end
329 internal
330 singleton
331 begin
332 end
333 internal
334 singleton
335 begin
336 end
337 internal
338 singleton
339 begin
340 end
341 internal
342 singleton
343 begin
344 end
345 internal
346 singleton
347 begin
348 end
349 internal
350 singleton
351 begin
352 end
353 internal
354 singleton
355 begin
356 end
357 internal
358 singleton
359 begin
360 end
361 internal
362 singleton
363 begin
364 end
365 internal
366 singleton
367 begin
368 end
369 internal
370 singleton
371 begin
372 end
373 internal
374 singleton
375 begin
376 end
377 internal
378 singleton
379 begin
380 end
381 internal
382 singleton
383 begin
384 end
385 internal
386 singleton
387 begin
388 end
389 internal
390 singleton
391 begin
392 end
393 internal
394 singleton
395 begin
396 end
397 internal
398 singleton
399 begin
400 end
401 internal
402 singleton
403 begin
404 end
405 internal
406 singleton
407 begin
408 end
409 internal
410 singleton

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,3 @@
[
5.71762e+10 -1.787531e+09 4.831925e+08 4.409361e+09 -1.932859e+09 -1.543599e+09 -3.678115e+09 -3.702029e+09 -3.746038e+09 -1.137944e+09 -9.079639e+08 -5.819128e+07 -2.0947e+09 1.309573e+09 -5.171984e+08 2.481896e+08 -8.396431e+08 1.52164e+08 -6.130926e+08 1.542617e+07 -2.69509e+08 -2281178 -6.453142e+07 7354178 1.033131e+08 -1.133166e+07 6.952426e+07 -6.868613e+07 8611552 -7.709294e+07 6.473713e+08
5.228252e+12 2.646175e+11 1.876415e+11 2.966005e+11 2.523884e+11 2.051858e+11 2.541094e+11 2.495341e+11 2.292826e+11 2.018152e+11 1.897722e+11 1.747636e+11 1.580083e+11 1.184324e+11 8.987214e+10 7.035342e+10 5.102559e+10 3.405446e+10 2.154957e+10 1.188595e+10 5.606266e+09 1.907599e+09 2.56771e+08 7.379798e+07 9.136799e+08 2.368205e+09 3.935658e+09 5.588645e+09 7.453864e+09 8.618391e+09 0 ]

View File

@@ -0,0 +1,2 @@
--left-context=3
--right-context=3

View File

@@ -0,0 +1 @@
123

View File

@@ -0,0 +1,185 @@
package com.example.jarvis_stts
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import org.vosk.Model
import org.vosk.Recognizer
import org.vosk.android.RecognitionListener
import org.vosk.android.SpeechService
import java.io.File
import android.content.pm.ServiceInfo
import org.json.JSONObject
class JarvisService : Service(), RecognitionListener {
private var voskService: SpeechService? = null
private var voskModel: Model? = null
private var isInteracting = false
companion object {
const val CHANNEL_ID = "JarvisServiceChannel"
const val ACTION_START = "ACTION_START"
const val ACTION_PAUSE = "ACTION_PAUSE"
const val ACTION_RESUME = "ACTION_RESUME"
}
override fun onCreate() {
super.onCreate()
createNotificationChannel()
// Für Android 14 (API 34) und höher müssen wir den Typ beim Starten mitgeben
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startForeground(
1,
createNotification("JARVIS hört zu..."),
ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
)
} else {
startForeground(1, createNotification("JARVIS hört zu..."))
}
initVosk()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
ACTION_START -> resumeListening()
ACTION_PAUSE -> pauseListening()
ACTION_RESUME -> resumeListening()
}
return START_STICKY
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"JARVIS Background Service",
NotificationManager.IMPORTANCE_LOW
)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(channel)
}
}
private fun createNotification(text: String): android.app.Notification {
val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE
)
return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("JARVIS ist aktiv")
.setContentText(text)
.setSmallIcon(android.R.drawable.ic_btn_speak_now) // Standard Android Icon
.setContentIntent(pendingIntent)
.setOngoing(true)
.build()
}
private fun updateNotification(text: String) {
val manager = getSystemService(NotificationManager::class.java)
manager.notify(1, createNotification(text))
}
private fun initVosk() {
val baseDir = getExternalFilesDir(null)
val modelFolder = File(baseDir, "model/model-de")
if (modelFolder.exists()) {
Log.d("JARVIS", "Modell gefunden unter: ${modelFolder.absolutePath}")
try {
// Wir laden das Modell und speichern es in der Klassen-Variable 'voskModel'
voskModel = Model(modelFolder.absolutePath)
Log.d("JARVIS", "Modell bereit, starte Hintergrund-Dienst...")
// Jetzt, wo das Modell da ist, können wir das Zuhören starten
resumeListening()
} catch (e: Exception) {
Log.e("JARVIS", "Vosk Fehler beim Modell-Laden: ${e.message}")
updateNotification("Fehler: Modell konnte nicht geladen werden")
}
} else {
Log.e("JARVIS", "ORDNER NICHT GEFUNDEN! Pfad: ${modelFolder.absolutePath}")
updateNotification("Fehler: Modell-Ordner fehlt")
}
}
private fun pauseListening() {
Log.d("JARVIS", "Service: Pausiere Zuhören")
voskService?.stop()
updateNotification("Pausiert (verarbeitet Anfrage...)")
}
private fun resumeListening() {
if (voskModel == null) return
Log.d("JARVIS", "Service: Starte Zuhören (Volles Wörterbuch)")
voskService?.stop()
// GANZ WICHTIG: Keine Grammar (Wortliste) mehr übergeben!
// Wir nutzen das volle deutsche Vokabular, um Fehlinterpretationen zu vermeiden.
val rec = Recognizer(voskModel, 16000.0f)
voskService = SpeechService(rec, 16000.0f)
voskService?.startListening(this)
isInteracting = false
updateNotification("Warte auf 'Jarvis'")
}
override fun onPartialResult(hypothesis: String) {
if (isInteracting) return
try {
val json = JSONObject(hypothesis)
val partialText = json.optString("partial").lowercase().trim()
// Wir ignorieren leere Strings und bekannte Rausch-Wörter für das Log
if (partialText.isNotEmpty() && partialText != "nun" && partialText.length > 2) {
Log.d("JARVIS_DEBUG", "Vosk hört: $partialText")
}
// Unser Regex-Check (den passen wir gleich an)
if (Regex("\\b(hey joe avis|hey gravis|bei gravis|hey joe|hey ich habe es|naja bis|welche avis|jarvis)\\b").containsMatchIn(partialText)) {
Log.d("JARVIS", "WAKE WORD ERKANNT: $partialText")
triggerJarvis()
}
} catch (e: Exception) {
Log.e("JARVIS", "Fehler beim Parsen: ${e.message}")
}
}
// onResult bleibt leer oder loggt nur zur Kontrolle
override fun onResult(hypothesis: String) {}
private fun triggerJarvis() {
isInteracting = true
pauseListening()
val intent = Intent(this, MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
putExtra("WAKE_WORD_TRIGGERED", true)
}
startActivity(intent)
}
override fun onFinalResult(hypothesis: String) {}
override fun onError(e: Exception) { Log.e("JARVIS", "Service Error: ${e.message}") }
override fun onTimeout() {}
override fun onDestroy() {
super.onDestroy()
voskService?.stop()
voskService?.shutdown()
}
override fun onBind(intent: Intent?): IBinder? = null
}

View File

@@ -3,9 +3,11 @@ package com.example.jarvis_stts
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.speech.RecognizerIntent
import android.speech.tts.TextToSpeech
import android.speech.tts.UtteranceProgressListener
import android.speech.tts.Voice
import android.util.Log
import android.view.View
@@ -16,42 +18,58 @@ import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import okhttp3.*
import org.vosk.Model
import org.vosk.Recognizer
import org.vosk.android.RecognitionListener
import org.vosk.android.SpeechService
import org.vosk.android.StorageService
import java.io.IOException
import java.util.Locale
import android.provider.Settings
import android.net.Uri
class MainActivity : AppCompatActivity(), RecognitionListener, TextToSpeech.OnInitListener {
class MainActivity : AppCompatActivity(), TextToSpeech.OnInitListener {
// UI Elemente
private lateinit var tvStatus: TextView
private lateinit var etUrl: EditText
private lateinit var spinnerVoices: Spinner
private lateinit var tts: TextToSpeech
// Vosk & Netzwerk
private var voskService: SpeechService? = null
private var voskModel: Model? = null
private val client = OkHttpClient()
private var webSocket: WebSocket? = null
// TTS Stimmen
private var availableVoices = mutableListOf<Voice>()
private var voiceNames = mutableListOf<String>()
// Launcher für Google Spracherkennung
private val speechRecognizerLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
// Nach der Google-Eingabe starten wir Vosk wieder
startVosk()
if (result.resultCode == RESULT_OK && result.data != null) {
val spokenText = result.data!!.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.get(0) ?: ""
tvStatus.text = "Ich: $spokenText"
if (webSocket != null) {
webSocket?.send(spokenText)
Log.d("JARVIS", "Sende an Server: $spokenText")
} else {
tvStatus.text = "Fehler: Nicht verbunden!"
// Falls nicht verbunden, Dienst wieder starten
tellServiceTo(JarvisService.ACTION_RESUME)
}
} else {
tellServiceTo(JarvisService.ACTION_RESUME)
}
}
private fun checkOverlayPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
Log.d("JARVIS", "Overlay-Berechtigung fehlt. Öffne Einstellungen...")
val intent = Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:$packageName")
)
// Wirft den Nutzer in die Einstellungen.
// Nach der Rückkehr muss die App meist neu gestartet/fokussiert werden.
startActivity(intent)
Toast.makeText(this, "Bitte erlaube JARVIS-AI, über anderen Apps zu erscheinen", Toast.LENGTH_LONG).show()
}
}
}
@@ -59,17 +77,22 @@ class MainActivity : AppCompatActivity(), RecognitionListener, TextToSpeech.OnIn
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 1. UI initialisieren
tvStatus = findViewById(R.id.tvStatus)
etUrl = findViewById(R.id.etUrl)
spinnerVoices = findViewById(R.id.spinnerVoices)
val btnConnect = findViewById<Button>(R.id.btnConnect)
val btnSpeak = findViewById<Button>(R.id.btnSpeak)
val prefs = getSharedPreferences("JarvisPrefs", MODE_PRIVATE)
tts = TextToSpeech(this, this)
val savedUrl = prefs.getString("server_url", "")
etUrl.setText(savedUrl)
// AUTOMATISCH VERBINDEN, falls eine URL gespeichert ist
if (!savedUrl.isNullOrEmpty()) {
connectToServer(savedUrl)
}
// 2. SharedPreferences
val prefs = getSharedPreferences("JarvisPrefs", MODE_PRIVATE)
etUrl.setText(prefs.getString("server_url", ""))
btnConnect.setOnClickListener {
@@ -80,66 +103,91 @@ class MainActivity : AppCompatActivity(), RecognitionListener, TextToSpeech.OnIn
}
}
btnSpeak.setOnClickListener { startVoiceInput() }
btnSpeak.setOnClickListener {
tellServiceTo(JarvisService.ACTION_PAUSE)
startVoiceInput()
}
// 3. Vosk Modell laden & Berechtigungen
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO), 1)
checkPermissionsAndInit()
checkOverlayPermission() // <-- Hier aufrufen!
}
// Wird aufgerufen, wenn die App im Hintergrund war und vom Service geweckt wird
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (intent?.getBooleanExtra("WAKE_WORD_TRIGGERED", false) == true) {
Log.d("JARVIS", "MainActivity: Wake Word vom Service empfangen! Starte Google...")
// Kleiner Delay, damit die Audio-Hardware Zeit zum Umschalten hat
tvStatus.postDelayed({
startVoiceInput()
}, 500)
}
}
private fun checkPermissionsAndInit() {
val permissions = mutableListOf(Manifest.permission.RECORD_AUDIO)
// Notification Permission für Android 13+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissions.add(Manifest.permission.POST_NOTIFICATIONS)
}
val missingPermissions = permissions.filter {
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
}
if (missingPermissions.isNotEmpty()) {
ActivityCompat.requestPermissions(this, missingPermissions.toTypedArray(), 1)
} else {
initVoskModel()
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 1 && grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
initVoskModel()
}
}
private fun initVoskModel() {
StorageService.unpack(this, "model-de", "model",
{ model: Model ->
voskModel = model
startVosk()
{ _: Model ->
Log.d("JARVIS", "Modell bereit, starte Hintergrund-Dienst...")
tellServiceTo(JarvisService.ACTION_START)
tvStatus.text = "Service läuft im Hintergrund!"
},
{ exception: IOException ->
Log.e("JARVIS", "Vosk Fehler: ${exception.message}")
Log.e("JARVIS", "Vosk Entpack-Fehler: ${exception.message}")
tvStatus.text = "Fehler: Modell nicht gefunden"
}
)
}
private fun startVosk() {
try {
if (voskModel == null) return
// Wir horchen auf "jarvis". [unk] lässt unbekannte Wörter zu.
val rec = Recognizer(voskModel, 16000.0f, "[\"computer\", \"[unk]\"]")
//val rec = Recognizer(voskModel, 16000.0f, "[\"jarvis\", \"[unk]\"]")
voskService = SpeechService(rec, 16000.0f)
voskService?.startListening(this)
runOnUiThread { tvStatus.text = "Bereit (Warte auf 'Jarvis')" }
} catch (e: Exception) {
Log.e("JARVIS", "Vosk Start Fehler: ${e.message}")
private fun tellServiceTo(action: String) {
val intent = Intent(this, JarvisService::class.java).apply {
this.action = action
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}
}
// --- Vosk RecognitionListener ---
override fun onPartialResult(hypothesis: String) {
// Diese Zeile zeigt dir live im Logcat, was Vosk gerade verstanden hat:
Log.d("JARVIS", "Vosk hört: $hypothesis")
// Testweise auf "computer" hören
if (hypothesis.contains("computer", ignoreCase = true)) {
voskService?.stop()
startVoiceInput()
}
}
override fun onResult(hypothesis: String) {}
override fun onFinalResult(hypothesis: String) {}
override fun onError(e: Exception) { Log.e("JARVIS", "Vosk Error: ${e.message}") }
override fun onTimeout() {}
// --- Google STT & TTS ---
private fun startVoiceInput() {
val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply {
putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
putExtra(RecognizerIntent.EXTRA_LANGUAGE, "de-DE")
putExtra(RecognizerIntent.EXTRA_PROMPT, "Ich höre dir zu...")
putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS, 2000L)
putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS, 2000L)
}
try {
speechRecognizerLauncher.launch(intent)
} catch (e: Exception) {
tellServiceTo(JarvisService.ACTION_RESUME)
}
}
private fun connectToServer(url: String) {
@@ -150,9 +198,15 @@ class MainActivity : AppCompatActivity(), RecognitionListener, TextToSpeech.OnIn
runOnUiThread { tvStatus.text = "Verbunden!" }
}
override fun onMessage(webSocket: WebSocket, text: String) {
runOnUiThread { tvStatus.text = "J.A.R.V.I.S.: $text" }
runOnUiThread { tvStatus.text = "JARVIS-AI: $text" }
speakOut(text)
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
runOnUiThread {
tvStatus.text = "Verbindungsfehler!"
tellServiceTo(JarvisService.ACTION_RESUME)
}
}
})
}
@@ -160,6 +214,22 @@ class MainActivity : AppCompatActivity(), RecognitionListener, TextToSpeech.OnIn
if (status == TextToSpeech.SUCCESS) {
tts.language = Locale.GERMAN
setupVoiceSpinner()
tts.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
override fun onStart(utteranceId: String?) {}
override fun onDone(utteranceId: String?) {
if (utteranceId == "TTS_DONE") {
runOnUiThread {
// Wenn Jarvis fertig gesprochen hat, lauschen wir wieder!
tellServiceTo(JarvisService.ACTION_RESUME)
}
}
}
@Deprecated("Deprecated in Java")
override fun onError(utteranceId: String?) {
runOnUiThread { tellServiceTo(JarvisService.ACTION_RESUME) }
}
})
}
}
@@ -184,14 +254,13 @@ class MainActivity : AppCompatActivity(), RecognitionListener, TextToSpeech.OnIn
}
private fun speakOut(text: String) {
tts.speak(text, TextToSpeech.QUEUE_FLUSH, null, "")
tts.speak(text, TextToSpeech.QUEUE_FLUSH, null, "TTS_DONE")
}
override fun onDestroy() {
voskService?.stop()
voskService?.shutdown()
webSocket?.close(1000, "App Ende")
tts.shutdown()
// Wir lassen den Service ABSICHTLICH nicht stoppen, wenn die Activity zerstört wird!
super.onDestroy()
}
}

View File

@@ -48,6 +48,19 @@
android:layout_marginVertical="8dp"
android:background="#CCCCCC" />
<Button
android:id="@+id/btnSpeak"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="Sprechen" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginVertical="8dp"
android:background="#CCCCCC" />
<TextView
android:id="@+id/tvStatus"
android:layout_width="match_parent"
@@ -57,11 +70,4 @@
android:text="Nicht verbunden"
android:textSize="18sp" />
<Button
android:id="@+id/btnSpeak"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="Sprechen" />
</LinearLayout>

View File

@@ -23,5 +23,5 @@ dependencyResolutionManagement {
}
}
rootProject.name = "jarvis-stts"
rootProject.name = "jarvis-ai"
include(":app")