Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 02cac99ef9 | |||
| 2940ce0075 | |||
| d78b668e31 | |||
| a9115699db | |||
| f2bfd4bbc6 |
@@ -79,8 +79,8 @@ android {
|
|||||||
applicationId "com.ariacockpit"
|
applicationId "com.ariacockpit"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 10605
|
versionCode 10608
|
||||||
versionName "0.1.6.5"
|
versionName "0.1.6.8"
|
||||||
// Fallback fuer Libraries mit Product Flavors
|
// Fallback fuer Libraries mit Product Flavors
|
||||||
missingDimensionStrategy 'react-native-camera', 'general'
|
missingDimensionStrategy 'react-native-camera', 'general'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,20 @@
|
|||||||
<!-- Optional: GPS-Position der Frage anhaengen (nur wenn User in Settings aktiviert) -->
|
<!-- Optional: GPS-Position der Frage anhaengen (nur wenn User in Settings aktiviert) -->
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<!-- Background-Location ist OPT-IN (Settings → GPS auch im Hintergrund).
|
||||||
|
Muss vom User explizit in Android-Einstellungen auf "Immer erlauben"
|
||||||
|
gesetzt werden — kann nicht ueber den normalen Permission-Dialog
|
||||||
|
angefordert werden (Android 10+). Default: aus. -->
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||||
<!-- Foreground-Service damit TTS auch bei minimierter App weiterlaeuft.
|
<!-- Foreground-Service damit TTS auch bei minimierter App weiterlaeuft.
|
||||||
FOREGROUND_SERVICE_MICROPHONE ist Pflicht ab Android 14 wenn der
|
FOREGROUND_SERVICE_MICROPHONE ist Pflicht ab Android 14 wenn der
|
||||||
Service waehrend des Backgrounds aufs Mikro zugreift (Wake-Word,
|
Service waehrend des Backgrounds aufs Mikro zugreift (Wake-Word,
|
||||||
Aufnahme im Gespraechsmodus). -->
|
Aufnahme im Gespraechsmodus). LOCATION wird nur aktiv wenn der
|
||||||
|
User Background-GPS in Settings einschaltet. -->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<!-- WAKE_LOCK damit Wake-Word + JS-Bridge auch bei aus-Display und Doze
|
<!-- WAKE_LOCK damit Wake-Word + JS-Bridge auch bei aus-Display und Doze
|
||||||
arbeiten: ohne Lock pausiert Android die CPU, Native-AudioRecord
|
arbeiten: ohne Lock pausiert Android die CPU, Native-AudioRecord
|
||||||
@@ -57,6 +64,6 @@
|
|||||||
<service
|
<service
|
||||||
android:name=".AriaPlaybackService"
|
android:name=".AriaPlaybackService"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:foregroundServiceType="mediaPlayback|microphone" />
|
android:foregroundServiceType="mediaPlayback|microphone|location" />
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "aria-cockpit",
|
"name": "aria-cockpit",
|
||||||
"version": "0.1.6.5",
|
"version": "0.1.6.8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"android": "react-native run-android",
|
"android": "react-native run-android",
|
||||||
|
|||||||
@@ -1270,9 +1270,11 @@ const ChatScreen: React.FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubWake = wakeWordService.onWakeWord(async () => {
|
const unsubWake = wakeWordService.onWakeWord(async () => {
|
||||||
console.log('[Chat] Gespraechsmodus — starte Auto-Aufnahme');
|
console.log('[Chat] Gespraechsmodus — starte Auto-Aufnahme');
|
||||||
|
import('../services/logger').then(m => m.reportAppDebug('wake.cb', 'callback fired, calling startRecording')).catch(()=>{});
|
||||||
// Conversation-Window: User hat X Sekunden um anzufangen, sonst Konversation aus
|
// Conversation-Window: User hat X Sekunden um anzufangen, sonst Konversation aus
|
||||||
const windowMs = await loadConvWindowMs();
|
const windowMs = await loadConvWindowMs();
|
||||||
const started = await audioService.startRecording(true, windowMs);
|
const started = await audioService.startRecording(true, windowMs);
|
||||||
|
import('../services/logger').then(m => m.reportAppDebug('wake.cb', `startRecording returned ${started}`)).catch(()=>{});
|
||||||
if (started) {
|
if (started) {
|
||||||
// Erst JETZT signalisieren dass das Mikro wirklich offen ist —
|
// Erst JETZT signalisieren dass das Mikro wirklich offen ist —
|
||||||
// vorher war's noch in der Init-Phase. So weiss der User exakt
|
// vorher war's noch in der Init-Phase. So weiss der User exakt
|
||||||
@@ -1280,6 +1282,7 @@ const ChatScreen: React.FC = () => {
|
|||||||
// ueber Settings → Wake-Word abschaltbar.
|
// ueber Settings → Wake-Word abschaltbar.
|
||||||
ToastAndroid.show('🎤 Mikro offen — sprich jetzt', ToastAndroid.SHORT);
|
ToastAndroid.show('🎤 Mikro offen — sprich jetzt', ToastAndroid.SHORT);
|
||||||
playWakeReadySound().catch(() => {});
|
playWakeReadySound().catch(() => {});
|
||||||
|
import('../services/logger').then(m => m.reportAppDebug('wake.cb', 'gong played + recording started')).catch(()=>{});
|
||||||
} else {
|
} else {
|
||||||
// Mikrofon nicht verfuegbar, naechsten Versuch
|
// Mikrofon nicht verfuegbar, naechsten Versuch
|
||||||
wakeWordService.resume();
|
wakeWordService.resume();
|
||||||
|
|||||||
@@ -52,7 +52,11 @@ import {
|
|||||||
TTS_SPEED_STORAGE_KEY,
|
TTS_SPEED_STORAGE_KEY,
|
||||||
} from '../services/audio';
|
} from '../services/audio';
|
||||||
import audioService from '../services/audio';
|
import audioService from '../services/audio';
|
||||||
import gpsTrackingService from '../services/gpsTracking';
|
import gpsTrackingService, {
|
||||||
|
isBackgroundGpsEnabled,
|
||||||
|
setBackgroundGpsEnabled,
|
||||||
|
ensureBackgroundLocationPermission,
|
||||||
|
} from '../services/gpsTracking';
|
||||||
import { acquireBackgroundAudio, releaseBackgroundAudio } from '../services/backgroundAudio';
|
import { acquireBackgroundAudio, releaseBackgroundAudio } from '../services/backgroundAudio';
|
||||||
import MemoryBrowser from '../components/MemoryBrowser';
|
import MemoryBrowser from '../components/MemoryBrowser';
|
||||||
import TriggerBrowser from '../components/TriggerBrowser';
|
import TriggerBrowser from '../components/TriggerBrowser';
|
||||||
@@ -134,6 +138,7 @@ const SettingsScreen: React.FC = () => {
|
|||||||
const [currentMode, setCurrentMode] = useState('normal');
|
const [currentMode, setCurrentMode] = useState('normal');
|
||||||
const [gpsEnabled, setGpsEnabled] = useState(false);
|
const [gpsEnabled, setGpsEnabled] = useState(false);
|
||||||
const [gpsTracking, setGpsTracking] = useState(gpsTrackingService.isActive());
|
const [gpsTracking, setGpsTracking] = useState(gpsTrackingService.isActive());
|
||||||
|
const [bgGpsEnabled, setBgGpsEnabled] = useState(false);
|
||||||
const [backgroundMode, setBackgroundMode] = useState(true); // Default an
|
const [backgroundMode, setBackgroundMode] = useState(true); // Default an
|
||||||
const [showSystemHints, setShowSystemHints] = useState(false); // Default aus
|
const [showSystemHints, setShowSystemHints] = useState(false); // Default aus
|
||||||
const [scannerVisible, setScannerVisible] = useState(false);
|
const [scannerVisible, setScannerVisible] = useState(false);
|
||||||
@@ -216,6 +221,8 @@ const SettingsScreen: React.FC = () => {
|
|||||||
const offGps = gpsTrackingService.onChange(setGpsTracking);
|
const offGps = gpsTrackingService.onChange(setGpsTracking);
|
||||||
// Persistierten Status wiederherstellen (war Tracking beim letzten Mal an?)
|
// Persistierten Status wiederherstellen (war Tracking beim letzten Mal an?)
|
||||||
gpsTrackingService.restoreFromStorage().catch(() => {});
|
gpsTrackingService.restoreFromStorage().catch(() => {});
|
||||||
|
// Background-GPS-Toggle initial laden
|
||||||
|
isBackgroundGpsEnabled().then(setBgGpsEnabled).catch(() => {});
|
||||||
AsyncStorage.getItem(TTS_PREROLL_STORAGE_KEY).then(saved => {
|
AsyncStorage.getItem(TTS_PREROLL_STORAGE_KEY).then(saved => {
|
||||||
if (saved != null) {
|
if (saved != null) {
|
||||||
const n = parseFloat(saved);
|
const n = parseFloat(saved);
|
||||||
@@ -1117,6 +1124,52 @@ const SettingsScreen: React.FC = () => {
|
|||||||
thumbColor={gpsTracking ? '#FFFFFF' : '#666680'}
|
thumbColor={gpsTracking ? '#FFFFFF' : '#666680'}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* Background-GPS opt-in — Default AUS. Braucht ACCESS_BACKGROUND_LOCATION
|
||||||
|
(User muss in Android-Settings 'Immer erlauben' aktivieren). */}
|
||||||
|
<View style={[styles.toggleRow, {marginTop: 12, borderTopWidth: 1, borderTopColor: '#1E1E2E', paddingTop: 12}]}>
|
||||||
|
<View style={styles.toggleInfo}>
|
||||||
|
<Text style={styles.toggleLabel}>GPS auch im Hintergrund</Text>
|
||||||
|
<Text style={styles.toggleHint}>
|
||||||
|
Damit ARIA auch unterwegs deine aktuelle Position kennt wenn die
|
||||||
|
App im Hintergrund ist (Auto, Handy-Tasche). Standard: aus.
|
||||||
|
{'\n\n'}
|
||||||
|
Android verlangt fuer Background-GPS, dass du in den
|
||||||
|
System-Einstellungen unter Standort "Immer erlauben" auswaehlst.
|
||||||
|
Beim Aktivieren wird Android-Settings geoeffnet falls noetig.
|
||||||
|
{'\n\n'}
|
||||||
|
Akku-Verbrauch: ~3-5% mehr pro Tag durch dauerhaftes Polling.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<Switch
|
||||||
|
value={bgGpsEnabled}
|
||||||
|
onValueChange={async (v) => {
|
||||||
|
if (v) {
|
||||||
|
const ok = await ensureBackgroundLocationPermission();
|
||||||
|
if (!ok) {
|
||||||
|
// User muss in Android-Settings auf "Immer erlauben" — Toggle
|
||||||
|
// bleibt aus bis er zurueckkommt und nochmal tippt.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await setBackgroundGpsEnabled(true);
|
||||||
|
setBgGpsEnabled(true);
|
||||||
|
// Wenn Tracking bereits laeuft: neu starten damit der
|
||||||
|
// Foreground-Service jetzt mit location-Slot kommt
|
||||||
|
if (gpsTrackingService.isActive()) {
|
||||||
|
gpsTrackingService.stop('bg-toggle');
|
||||||
|
gpsTrackingService.start('bg-aktiviert').catch(() => {});
|
||||||
|
}
|
||||||
|
ToastAndroid.show('Background-GPS aktiviert', ToastAndroid.SHORT);
|
||||||
|
} else {
|
||||||
|
await setBackgroundGpsEnabled(false);
|
||||||
|
setBgGpsEnabled(false);
|
||||||
|
ToastAndroid.show('Background-GPS aus — nur noch Foreground', ToastAndroid.SHORT);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
trackColor={{ false: '#2A2A3E', true: '#FF3B30' }}
|
||||||
|
thumbColor={bgGpsEnabled ? '#FFFFFF' : '#666680'}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* === Bubble-Anzeige === */}
|
{/* === Bubble-Anzeige === */}
|
||||||
|
|||||||
@@ -9,13 +9,14 @@
|
|||||||
* - 'tts' : ARIA spricht
|
* - 'tts' : ARIA spricht
|
||||||
* - 'rec' : Aufnahme laeuft
|
* - 'rec' : Aufnahme laeuft
|
||||||
* - 'wake' : Wake-Word lauscht passiv (Ohr aktiv)
|
* - 'wake' : Wake-Word lauscht passiv (Ohr aktiv)
|
||||||
|
* - 'location' : Background-GPS-Tracking (opt-in in Settings)
|
||||||
* - 'background' : Persistenter Hintergrund-Modus (Settings-Toggle).
|
* - 'background' : Persistenter Hintergrund-Modus (Settings-Toggle).
|
||||||
* Haelt JS-Engine + WebSocket auch ohne Audio am Leben
|
* Haelt JS-Engine + WebSocket auch ohne Audio am Leben
|
||||||
* → Trigger-Replies, Reconnects, Push-Reaktionen.
|
* → Trigger-Replies, Reconnects, Push-Reaktionen.
|
||||||
*
|
*
|
||||||
* Solange mindestens ein Slot aktiv ist, laeuft der Service. Wenn alle
|
* Solange mindestens ein Slot aktiv ist, laeuft der Service. Wenn alle
|
||||||
* Slots leer sind, wird er gestoppt. Der Notification-Text passt sich an
|
* Slots leer sind, wird er gestoppt. Der Notification-Text passt sich an
|
||||||
* den hoechstprioren Slot an (tts > rec > wake > background).
|
* den hoechstprioren Slot an (tts > rec > wake > location > background).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { NativeModules } from 'react-native';
|
import { NativeModules } from 'react-native';
|
||||||
@@ -27,13 +28,13 @@ interface BackgroundAudioNative {
|
|||||||
|
|
||||||
const { BackgroundAudio } = NativeModules as { BackgroundAudio?: BackgroundAudioNative };
|
const { BackgroundAudio } = NativeModules as { BackgroundAudio?: BackgroundAudioNative };
|
||||||
|
|
||||||
type Slot = 'tts' | 'rec' | 'wake' | 'background';
|
type Slot = 'tts' | 'rec' | 'wake' | 'location' | 'background';
|
||||||
|
|
||||||
const slots = new Set<Slot>();
|
const slots = new Set<Slot>();
|
||||||
|
|
||||||
// Prioritaet fuer den Notification-Text — hoechste zuerst. 'background'
|
// Prioritaet fuer den Notification-Text — hoechste zuerst. 'background'
|
||||||
// ist die fallback-Anzeige wenn nichts anderes laeuft.
|
// ist die fallback-Anzeige wenn nichts anderes laeuft.
|
||||||
const PRIORITY: Slot[] = ['tts', 'rec', 'wake', 'background'];
|
const PRIORITY: Slot[] = ['tts', 'rec', 'wake', 'location', 'background'];
|
||||||
|
|
||||||
function topReason(): string {
|
function topReason(): string {
|
||||||
for (const s of PRIORITY) {
|
for (const s of PRIORITY) {
|
||||||
@@ -47,6 +48,7 @@ async function applyState(): Promise<void> {
|
|||||||
if (slots.size === 0) {
|
if (slots.size === 0) {
|
||||||
try { await BackgroundAudio.stop(); } catch {}
|
try { await BackgroundAudio.stop(); } catch {}
|
||||||
console.log('[BackgroundAudio] Service gestoppt (keine Slots)');
|
console.log('[BackgroundAudio] Service gestoppt (keine Slots)');
|
||||||
|
import('./logger').then(m => m.reportAppDebug('bg.stop', 'service stopped')).catch(()=>{});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const reason = topReason();
|
const reason = topReason();
|
||||||
@@ -54,8 +56,10 @@ async function applyState(): Promise<void> {
|
|||||||
await BackgroundAudio.start(reason);
|
await BackgroundAudio.start(reason);
|
||||||
console.log('[BackgroundAudio] Service aktiv (slot=%s, slots=%s)',
|
console.log('[BackgroundAudio] Service aktiv (slot=%s, slots=%s)',
|
||||||
reason, [...slots].join('+'));
|
reason, [...slots].join('+'));
|
||||||
|
import('./logger').then(m => m.reportAppDebug('bg.start', `slot=${reason} all=[${[...slots].join(',')}]`)).catch(()=>{});
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.warn('[BackgroundAudio] start fehlgeschlagen:', err?.message || err);
|
console.warn('[BackgroundAudio] start fehlgeschlagen:', err?.message || err);
|
||||||
|
import('./logger').then(m => m.reportAppDebug('bg.start.fail', err?.message || String(err))).catch(()=>{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,62 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import { PermissionsAndroid, Platform, ToastAndroid } from 'react-native';
|
import { Linking, PermissionsAndroid, Platform, ToastAndroid } from 'react-native';
|
||||||
import Geolocation from '@react-native-community/geolocation';
|
import Geolocation from '@react-native-community/geolocation';
|
||||||
import rvs from './rvs';
|
import rvs from './rvs';
|
||||||
|
import { acquireBackgroundAudio, releaseBackgroundAudio } from './backgroundAudio';
|
||||||
|
|
||||||
|
// Opt-in Background-GPS — Settings-Toggle "GPS auch im Hintergrund".
|
||||||
|
// Default AUS. Wenn AN: ACCESS_BACKGROUND_LOCATION-Permission noetig
|
||||||
|
// (kann nicht ueber Standard-Dialog angefordert werden, User muss in
|
||||||
|
// Android-Settings auf "Immer erlauben" gehen) + ForegroundService mit
|
||||||
|
// foregroundServiceType=location wird hochgezogen.
|
||||||
|
export const BG_GPS_STORAGE_KEY = 'aria_gps_background_enabled';
|
||||||
|
|
||||||
|
export async function isBackgroundGpsEnabled(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const v = await AsyncStorage.getItem(BG_GPS_STORAGE_KEY);
|
||||||
|
return v === 'true';
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setBackgroundGpsEnabled(enabled: boolean): Promise<void> {
|
||||||
|
try {
|
||||||
|
await AsyncStorage.setItem(BG_GPS_STORAGE_KEY, String(enabled));
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Prueft ob ACCESS_BACKGROUND_LOCATION gewaehrt ist und oeffnet sonst die
|
||||||
|
* Android-App-Settings damit der User "Immer erlauben" auswaehlen kann.
|
||||||
|
* Returns true wenn permission ok, false wenn User Settings oeffnen muss. */
|
||||||
|
export async function ensureBackgroundLocationPermission(): Promise<boolean> {
|
||||||
|
if (Platform.OS !== 'android') return true;
|
||||||
|
try {
|
||||||
|
const granted = await PermissionsAndroid.check(
|
||||||
|
'android.permission.ACCESS_BACKGROUND_LOCATION' as any,
|
||||||
|
);
|
||||||
|
if (granted) return true;
|
||||||
|
// Erst FINE_LOCATION anfordern falls noch nicht da
|
||||||
|
const fine = await PermissionsAndroid.request(
|
||||||
|
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
|
||||||
|
);
|
||||||
|
if (fine !== PermissionsAndroid.RESULTS.GRANTED) return false;
|
||||||
|
// Ab Android 10+ kann BACKGROUND_LOCATION NICHT ueber den normalen
|
||||||
|
// PermissionsAndroid.request abgefragt werden — User muss in Settings
|
||||||
|
// auf "Immer erlauben" wechseln. Wir oeffnen die App-Settings-Seite.
|
||||||
|
ToastAndroid.show(
|
||||||
|
'Bitte in Android-Einstellungen unter Standort "Immer erlauben" auswaehlen',
|
||||||
|
ToastAndroid.LONG,
|
||||||
|
);
|
||||||
|
Linking.openSettings();
|
||||||
|
return false;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[gps-track] BG-Permission-Check fehlgeschlagen:', e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Listener = (active: boolean) => void;
|
type Listener = (active: boolean) => void;
|
||||||
|
|
||||||
@@ -86,6 +139,14 @@ class GpsTrackingService {
|
|||||||
ToastAndroid.show('GPS-Tracking: Berechtigung abgelehnt', ToastAndroid.LONG);
|
ToastAndroid.show('GPS-Tracking: Berechtigung abgelehnt', ToastAndroid.LONG);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// Background-GPS opt-in: wenn aktiv, ForegroundService mit type=location
|
||||||
|
// hochziehen. Brauche ACCESS_BACKGROUND_LOCATION (User muss in Android-
|
||||||
|
// Settings 'Immer erlauben' aktivieren). Wenn die fehlt, watchPosition
|
||||||
|
// liefert im Hintergrund keine Updates (nur Heartbeat sendet alte Werte).
|
||||||
|
const bgEnabled = await isBackgroundGpsEnabled();
|
||||||
|
if (bgEnabled) {
|
||||||
|
try { await acquireBackgroundAudio('location'); } catch {}
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
this.watchId = Geolocation.watchPosition(
|
this.watchId = Geolocation.watchPosition(
|
||||||
(pos) => {
|
(pos) => {
|
||||||
@@ -142,6 +203,8 @@ class GpsTrackingService {
|
|||||||
clearInterval(this.heartbeatTimer);
|
clearInterval(this.heartbeatTimer);
|
||||||
this.heartbeatTimer = null;
|
this.heartbeatTimer = null;
|
||||||
}
|
}
|
||||||
|
// Location-Foreground-Service-Slot freigeben (falls vorher acquired)
|
||||||
|
try { releaseBackgroundAudio('location'); } catch {}
|
||||||
this.active = false;
|
this.active = false;
|
||||||
this.lastChangeAt = Date.now();
|
this.lastChangeAt = Date.now();
|
||||||
this.notify();
|
this.notify();
|
||||||
|
|||||||
@@ -78,6 +78,22 @@ export function reportAppError(ev: AppErrorEvent): void {
|
|||||||
console.error(`[app-error scope=${ev.scope}]`, ev.message, '\n', ev.stack || '');
|
console.error(`[app-error scope=${ev.scope}]`, ev.message, '\n', ev.stack || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Schickt eine Debug-/Info-Message via RVS an die Bridge. Landet ebenfalls
|
||||||
|
* in /shared/logs/app.log — abrufbar via `curl /api/app-log?lines=N`.
|
||||||
|
* Im Gegensatz zu reportAppError: keine Stacktrace, level=info, kein
|
||||||
|
* console.error. Fuer Live-Diagnose im Hintergrund wenn ADB nicht da ist. */
|
||||||
|
export function reportAppDebug(scope: string, message: string): void {
|
||||||
|
try {
|
||||||
|
rvs.send('app_log' as any, {
|
||||||
|
ts: Date.now(),
|
||||||
|
platform: Platform.OS,
|
||||||
|
level: 'info',
|
||||||
|
scope,
|
||||||
|
message: String(message).slice(0, 2000),
|
||||||
|
});
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
/** Installiert einen globalen JS-Error-Handler der ungefangene Errors via
|
/** Installiert einen globalen JS-Error-Handler der ungefangene Errors via
|
||||||
* RVS an die Bridge schickt. Beim App-Start aufrufen. */
|
* RVS an die Bridge schickt. Beim App-Start aufrufen. */
|
||||||
export function installGlobalCrashReporter(): void {
|
export function installGlobalCrashReporter(): void {
|
||||||
|
|||||||
@@ -179,6 +179,8 @@ class WakeWordService {
|
|||||||
try {
|
try {
|
||||||
await OpenWakeWord.start();
|
await OpenWakeWord.start();
|
||||||
console.log('[WakeWord] armed — warte auf "%s"', this.keyword);
|
console.log('[WakeWord] armed — warte auf "%s"', this.keyword);
|
||||||
|
// Debug-Log via RVS damit wir auch ohne ADB sehen wann es greift
|
||||||
|
import('./logger').then(m => m.reportAppDebug('wake.start', `armed, keyword=${this.keyword}`)).catch(()=>{});
|
||||||
ToastAndroid.show(`Lausche auf "${KEYWORD_LABELS[this.keyword]}"`, ToastAndroid.SHORT);
|
ToastAndroid.show(`Lausche auf "${KEYWORD_LABELS[this.keyword]}"`, ToastAndroid.SHORT);
|
||||||
this.setState('armed');
|
this.setState('armed');
|
||||||
return true;
|
return true;
|
||||||
@@ -236,6 +238,8 @@ class WakeWordService {
|
|||||||
}
|
}
|
||||||
console.log('[WakeWord] Wake-Word "%s" erkannt! (state=%s, barge=%s)',
|
console.log('[WakeWord] Wake-Word "%s" erkannt! (state=%s, barge=%s)',
|
||||||
this.keyword, this.state, this.bargeListening);
|
this.keyword, this.state, this.bargeListening);
|
||||||
|
import('./logger').then(m => m.reportAppDebug('wake.detect',
|
||||||
|
`keyword=${this.keyword} state=${this.state} barge=${this.bargeListening}`)).catch(()=>{});
|
||||||
this.lastTriggerAt = now;
|
this.lastTriggerAt = now;
|
||||||
if (this.nativeReady && OpenWakeWord) {
|
if (this.nativeReady && OpenWakeWord) {
|
||||||
try { await OpenWakeWord.stop(); } catch {}
|
try { await OpenWakeWord.stop(); } catch {}
|
||||||
|
|||||||
Reference in New Issue
Block a user