herold

07 — Erkenntnisse

Das hier ist der wertvollste Teil des Repos: Dinge, die nirgends dokumentiert sind und die jeweils Stunden gekostet haben. Wenn du an einer Galaxy Watch ohne Google-Konto arbeitest, spart dir diese Seite den größten Teil davon.


1. Ohne diesen Schalter schlägt jede Installation fehl

Symptom: Jede App-Installation hängt oder bricht ab — bei Aurora Store genauso wie bei einem eigenen Laden. Der Bestätigungsdialog erscheint gar nicht.

Im Log:

SecurityException: Can't install packages while in secure FRP

Ursache: Die Uhr steckt im Werksschutz-Modus (Factory Reset Protection), obwohl die Einrichtung abgeschlossen ist — vermutlich, weil sie ohne Google-Konto eingerichtet wurde. In diesem Zustand blockiert der PackageManager alles.

Lösung:

adb shell settings put global secure_frp_mode 0
adb shell settings put secure secure_frp_mode 0

Danach läuft die Installation normal. Der Wert bleibt gesetzt; nach einem Systemupdate lohnt eine erneute Prüfung. Markt setzt ihn beim Start selbst zurück (braucht dafür WRITE_SECURE_SETTINGS, per adb pm grant).

Merke: Bei „Installation hängt” immer zuerst secure_frp_mode prüfen — nicht den eigenen Installer verdächtigen.


2. Die Dienst-Benachrichtigung verschwinden lassen

Ein Vordergrunddienst muss eine Benachrichtigung zeigen. Auf One UI Watch half nichts davon:

Was funktioniert: Die Benachrichtigung auf einen gesperrten Kanal legen (IMPORTANCE_NONE). Dann ist sie zwar technisch vorhanden, taucht aber in der Liste nicht auf.

nm.createNotificationChannel(new NotificationChannel(
        "app_stumm", "Dienst (stumm)", NotificationManager.IMPORTANCE_NONE));

Ein bestehender Kanal lässt sich nicht nachträglich leiser stellen — bei Bedarf löschen und mit neuer ID anlegen.

Prüfen, ob wirklich nichts angezeigt wird (verlässlicher als jede Geste):

adb shell cmd notification list        # taucht das eigene Paket auf?

3. Ein fremdes iPhone findet die Uhr nicht

Symptom: Das eigene iPhone zeigt die werbende Uhr, ein anderes iPhone zeigt gar nichts — obwohl es nie mit der Uhr verbunden war.

Zwei Ursachen, beide wichtig:

a) Der Name fehlte im Werbepaket. Ein BLE-Werbepaket fasst 31 Byte. Die 128-Bit-ANCS-Solicitation belegt 18, Flags 3 — es bleiben rund 8 Zeichen für den Namen. „Galaxy Watch6 Classic (A5TR)” passt nicht, also landete der Name in der Scan-Antwort. Ein Telefon, das das Gerät schon kennt, zeigt es trotzdem (es hat den Namen im Zwischenspeicher) — ein fremdes listet Geräte ohne Namen im Werbepaket nicht.

Lösung: kurzen Bluetooth-Namen setzen, dann passt er mit hinein:

if (adapter.getName().length() > 8) adapter.setName("Herold");
new AdvertiseData.Builder()
    .setIncludeDeviceName(true)
    .addServiceSolicitationUuid(new ParcelUuid(ANCS_SERVICE))
    .build();

Wird das Paket doch zu groß, meldet onStartFailure Code 1 — dann ohne Namen erneut versuchen (macht Herold automatisch).

b) Die Uhr war nie klassisch sichtbar.

ScanMode: SCAN_MODE_CONNECTABLE       # <- nicht auffindbar

iOS listet in den Bluetooth-Einstellungen praktisch nur klassisch sichtbare Geräte. Reine BLE-Werbung reicht einem unbekannten Telefon nicht:

adb shell am start -a android.bluetooth.adapter.action.REQUEST_DISCOVERABLE \
    --ei android.bluetooth.adapter.extra.DISCOVERABLE_DURATION 300

Und: Ein verbundenes Gerät stoppt die Werbung. Beim Koppeln mit Telefon B also Bluetooth an Telefon A ausschalten.


4. Ziffernblatt per adb aktiv setzen

adb shell am broadcast -a com.google.android.wearable.app.DEBUG_SURFACE \
  --es operation set-watchface \
  --ecn component <paket>/<klasse>

Antwort Favorite Id=[..] Runtime=[..] = gesetzt.

--ecn, nicht --es component. Mit --es antwortet die Uhr stur „Either component name or watchface id are required.” — der Extra muss ein ComponentName sein.


5. Play liefert nicht alles an eine kontenlose Uhr

Das ist die härteste Grenze des ganzen Projekts. Wer solche Dienste braucht, kommt um ein Google-Konto nicht herum.


6. Play-Suche und die Wear-Kategorien


7. Der Ladezustand lügt

BatteryManager.isCharging() meldet beim drahtlosen Laden „nein”. Richtig ist der klebrige Batterie-Intent:

Intent i = registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
int plugged = i.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
int status  = i.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean laedt = plugged != 0
        || status == BatteryManager.BATTERY_STATUS_CHARGING
        || status == BatteryManager.BATTERY_STATUS_FULL;

8. Sensoren: Statuscodes, die man kennen muss


9. Rhythmus: Atemarrhythmie ist kein Fehlalarm

Eine naive Analyse („schwankt stark → unregelmäßig”) meldet bei jungen, gesunden Menschen Fehlalarm: Beim Atmen schwankt der Puls stark, aber rhythmisch.

Der Unterschied liegt im Muster: Nachbarvorhersage über mehrere Abstände und spektrale Bandleistung. Ist die Schwankung vorhersagbar, ist es Atmung. Ist sie chaotisch und ohne Muster, ist sie unregelmäßig. Dazwischen gehört ein ehrliches „unsicher”.

Schwellen nicht raten — an vielen nachgebildeten Messreihen ablesen und gegen echte Aufzeichnungen prüfen (herold/test/).


10. Kleinkram, der Zeit frisst