diff --git a/README.md b/README.md
index 17b0dbc..4fc8f08 100644
--- a/README.md
+++ b/README.md
@@ -97,7 +97,7 @@ ARIA hat zwei Rollen:
| Compute-Node(s) (STT/TTS/LLM) | 1..n GPU-Rechner | `cd xtts && cp .env.example .env && docker compose up -d` |
| Satellit(en) π°οΈ (optional) | Fremdes Netz (BΓΌro β¦) | `cd satellite && cp .env.example .env && docker compose up -d --build` |
| Host-Agent(en) π» (optional) | Direkt auf einem Rechner | `cd host-agent && ./build.sh` β Binary + `.env` auf den Rechner, starten (Details unten & in [`host-agent/README.md`](host-agent/README.md)) |
-| Android-Agent π±πΊ (optional) | Handy, Tablet, Fire TV | `cd host-agent/android && ./build.sh` β APK, oder per Satellit via ADB ausrollen (Details unten) |
+| Android-Agent π±πΊ (optional) | Handy, Tablet, Fire TV | `cd host-agent/android && ./build.sh` β APK, oder per Satellit via ADB bzw. per MDM ausrollen (Details unten) |
| Android App | Stefans Handy | APK installieren (Auto-Update via RVS) |
> Compute-Nodes sind optional: ohne sie faellt STT auf lokales Whisper (CPU,
@@ -1267,6 +1267,65 @@ vorgeschlagen; nur wenn es keinen gibt, kommt der automatisch generierte.
---
+## Ausrollen per MDM π² β Cockpit-App und Android-Agent
+
+Beide Android-Apps lassen sich ueber ein MDM verteilen. Die Zugangsdaten gibt
+das MDM als **App-Konfiguration** (Android βManaged Configuration") mit β am
+Geraet muss niemand einen QR-Code scannen oder etwas eintippen.
+
+Im MDM pro App den **Paketnamen** eintragen und darunter die Werte als
+Schluessel / Typ / Wert:
+
+
+
+Der Typ wird pro Zeile aus *Text*, *Zahl* oder *Ja/Nein* gewaehlt:
+
+
+
+### Cockpit-App β Paketname `com.ariacockpit`
+
+| Schluessel | Typ | Wert | Pflicht |
+|------------|-----|------|---------|
+| `host` | Text | RVS-Hostname, z.B. `aria-rvs.example.de` (= `RVS_HOST`) | ja |
+| `token` | Text | Pairing-Token (= `RVS_TOKEN`, derselbe wie im QR-Code) | ja |
+| `port` | Zahl | `443` (= `RVS_PORT`) | nein, Standard 443 |
+| `tls` | Ja/Nein | Ja (= `RVS_TLS`) | nein, Standard Ja |
+
+### Android-Agent β Paketname `de.hackersoft.ariaagent`
+
+| Schluessel | Typ | Wert | Pflicht |
+|------------|-----|------|---------|
+| `host` | Text | RVS-Hostname (= `RVS_HOST`) | ja |
+| `token` | Text | Pairing-Token (= `RVS_TOKEN`) | ja |
+| `port` | Zahl | `443` | nein, Standard 443 |
+| `tls` | Ja/Nein | Ja | nein, Standard Ja |
+| `name` | Text | Geraetename, so erscheint er in der Diagnostic | nein, sonst der Modellname |
+| `control` | Ja/Nein | Ja = ARIA darf das Geraet steuern (tippen, wischen, Apps starten) | nein, Standard Nein |
+| `reply_mode` | Text | `overlay` (Einblendung) oder `speech` (Sprache) | nein, Standard `overlay` |
+
+### Was dabei passiert
+
+- **Nur mit `host` UND `token`** uebernimmt die App die Werte. Fehlt einer der
+ beiden, bleibt alles wie bisher (QR-Code bzw. Eingabe am Geraet).
+- **Das MDM hat Vorrang.** Eine Aenderung im MDM (z.B. neuer Token) ueberschreibt
+ die Werte am Geraet. Beim Agenten bleiben nicht gesetzte Schluessel auf ihrem
+ bisherigen Wert.
+- **Wann gelesen wird:** Cockpit beim App-Start, der Agent beim App-Start und
+ nach einem Neustart des Geraets. Eine Aenderung im MDM greift also erst danach.
+- **Was das MDM nicht erledigen kann:** Beim Agenten muessen die
+ Bedienungshilfe (fuer `control`), die Einblendung ueber anderen Apps und die
+ Bildschirm-Aufnahme weiterhin einmal am Geraet erlaubt werden β Android laesst
+ diese Spezialberechtigungen nicht per App-Konfiguration zu. Cockpit fragt
+ Mikrofon, Benachrichtigungen usw. wie gewohnt beim ersten Start.
+- Der **Token ist ein Zugangsschluessel**: Wer ihn kennt, landet im selben
+ RVS-Room. Im MDM nur fuer Admins sichtbar ablegen.
+
+Welche Schluessel es gibt, steht in `res/xml/app_restrictions.xml` der
+jeweiligen App (`android/android/app/...` bzw. `host-agent/android/app/...`);
+ein MDM, das die Liste selbst ausliest, zeigt sie dort auch mit Beschreibung an.
+
+---
+
## Docker Volumes
| Volume / Bind | Pfad im Container | Zweck |
diff --git a/android/android/app/src/main/AndroidManifest.xml b/android/android/app/src/main/AndroidManifest.xml
index c52bb4f..cea3178 100644
--- a/android/android/app/src/main/AndroidManifest.xml
+++ b/android/android/app/src/main/AndroidManifest.xml
@@ -38,6 +38,10 @@
android:allowBackup="false"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/AppTheme">
+
+
out.putString(key, v)
+ is Int -> out.putInt(key, v)
+ is Boolean -> out.putBoolean(key, v)
+ }
+ }
+ promise.resolve(out)
+ } catch (e: Exception) {
+ promise.resolve(Arguments.createMap())
+ }
+ }
+}
+
+class MdmConfigPackage : ReactPackage {
+ override fun createNativeModules(reactContext: ReactApplicationContext): List =
+ listOf(MdmConfigModule(reactContext))
+
+ override fun createViewManagers(reactContext: ReactApplicationContext): List> =
+ emptyList()
+}
diff --git a/android/android/app/src/main/res/values/mdm_strings.xml b/android/android/app/src/main/res/values/mdm_strings.xml
new file mode 100644
index 0000000..f631d50
--- /dev/null
+++ b/android/android/app/src/main/res/values/mdm_strings.xml
@@ -0,0 +1,10 @@
+
+
+
+ RVS-Host
+ Hostname des RVS, z.B. aria-rvs.example.de
+ RVS-Port
+ TLS verwenden
+ Token
+ Derselbe Token wie im QR-Code
+
diff --git a/android/android/app/src/main/res/xml/app_restrictions.xml b/android/android/app/src/main/res/xml/app_restrictions.xml
new file mode 100644
index 0000000..56c7e88
--- /dev/null
+++ b/android/android/app/src/main/res/xml/app_restrictions.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/android/src/services/rvs.ts b/android/src/services/rvs.ts
index 324a730..91a03d7 100644
--- a/android/src/services/rvs.ts
+++ b/android/src/services/rvs.ts
@@ -491,7 +491,40 @@ class RVSConnection {
}
}
+ /**
+ * Verbindungsdaten aus dem MDM (Managed Configuration), falls beim Ausrollen
+ * gesetzt. Nur gueltig mit host UND token. Schluessel: res/xml/app_restrictions.xml
+ */
+ async loadMdmConfig(): Promise {
+ try {
+ const Mdm: any = NativeModules.MdmConfig;
+ if (Platform.OS !== 'android' || !Mdm?.getConfig) return null;
+ const m = await Mdm.getConfig();
+ const host = String(m?.host ?? '').trim();
+ const token = String(m?.token ?? '').trim();
+ if (!host || !token) return null;
+ const port = Number(m.port);
+ return {
+ host,
+ token,
+ port: Number.isFinite(port) && port > 0 ? port : 443,
+ useTLS: m.tls !== false,
+ };
+ } catch {
+ return null;
+ }
+ }
+
async loadConfig(): Promise {
+ // Vom MDM gesetzte Werte haben Vorrang: so kommen geaenderte Zugangsdaten
+ // beim naechsten App-Start an, ohne dass jemand am Geraet etwas tun muss.
+ const mdm = await this.loadMdmConfig();
+ if (mdm) {
+ console.log('[RVS] Konfiguration aus dem MDM');
+ this.config = mdm;
+ await this.saveConfig(mdm);
+ return mdm;
+ }
try {
const data = await AsyncStorage.getItem(RVSConnection.STORAGE_KEY);
if (data) {
diff --git a/docs/images/mdm/mdm-app-konfiguration.png b/docs/images/mdm/mdm-app-konfiguration.png
new file mode 100644
index 0000000..b700059
Binary files /dev/null and b/docs/images/mdm/mdm-app-konfiguration.png differ
diff --git a/docs/images/mdm/mdm-typ-auswahl.png b/docs/images/mdm/mdm-typ-auswahl.png
new file mode 100644
index 0000000..68374ef
Binary files /dev/null and b/docs/images/mdm/mdm-typ-auswahl.png differ
diff --git a/host-agent/android/app/src/main/AndroidManifest.xml b/host-agent/android/app/src/main/AndroidManifest.xml
index 9d45bcc..a30ef16 100644
--- a/host-agent/android/app/src/main/AndroidManifest.xml
+++ b/host-agent/android/app/src/main/AndroidManifest.xml
@@ -37,6 +37,11 @@
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.DayNight">
+
+
+
0 } ?: 443 else old.port,
+ tls = if (b.containsKey("tls")) b.getBoolean("tls") else old.tls,
+ name = b.getString("name")?.trim().takeUnless { it.isNullOrEmpty() } ?: old.name,
+ controlEnabled = if (b.containsKey("control")) b.getBoolean("control") else old.controlEnabled,
+ replyMode = b.getString("reply_mode")?.trim()
+ ?.takeIf { it == "overlay" || it == "speech" } ?: old.replyMode,
+ )
+ if (cfg == old) return null
+ cfg.save(ctx)
+ return cfg
+ }
+}
diff --git a/host-agent/android/app/src/main/res/values/mdm_strings.xml b/host-agent/android/app/src/main/res/values/mdm_strings.xml
new file mode 100644
index 0000000..7443dd1
--- /dev/null
+++ b/host-agent/android/app/src/main/res/values/mdm_strings.xml
@@ -0,0 +1,15 @@
+
+
+
+ RVS-Host
+ Hostname des RVS, z.B. aria-rvs.example.de
+ RVS-Port
+ TLS verwenden
+ Token
+ Derselbe Token wie im QR-Code
+ Geraetename
+ So erscheint das Geraet in der Diagnostic (leer = Modellname)
+ Steuern erlauben
+ Antwort als
+ overlay (Einblendung) oder speech (Sprache)
+
diff --git a/host-agent/android/app/src/main/res/xml/app_restrictions.xml b/host-agent/android/app/src/main/res/xml/app_restrictions.xml
new file mode 100644
index 0000000..95bd122
--- /dev/null
+++ b/host-agent/android/app/src/main/res/xml/app_restrictions.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+