Android: Vorschauen, Aufnahmedienste, Push und Abrechnung
Der Android-Codebetrachter und seine geteilten TextMate-Grammatiken, ein WebView-Profil pro HTML-Dokument, PDF- und zwischengespeicherte Dateivorschauen, die Vordergrunddienste für Diktat und Review Mode, Push-Tokens und Google Play Billing.
Gegen den Quellcode geprüft am 17. September 2026
Auf dieser Seite
TextMate-Grammatiken, geteilt mit dem Desktop
Der Android-Codebetrachter verwendet den Grammatikbestand des Shiki-Highlighters auf dem Desktop. scripts/integrations/android_textmate/sync-android-textmate-grammars.mjs liest @shikijs/langs aus der Shiki-Installation des Desktops, löst 24 angebotene Sprachen in 33 Grammatiken samt eingebetteter auf, normalisiert ihre Capture-Maps für tm4e und schreibt die Grammatiken, ein Manifest mit einem SHA-256 pro Grammatik und der Quellversion, eine Sprachliste und die Lizenzen nach assets/textmate. Die aktuelle Quelle ist @shikijs/langs 4.2.0. Führen Sie das Skript aus, nachdem sich die Shiki-Version des Workspaces geändert hat.
node scripts/integrations/android_textmate/sync-android-textmate-grammars.mjsDie Hervorhebung nutzt Soras GrammarRegistry mit tm4e und nativem Oniguruma. Eine ganze Datei öffnet sich in einem schreibgeschützten Sora-CodeEditor, und ein Ausschnitt mit Zeilenziel erscheint als LazyColumn tokenisierter Zeilen. Die Hervorhebung läuft auf 2 geteilten Workern mit einem Cache für 24 Dokumente. Diffs verwenden denselben Betrachter mit der Diff-Grammatik, und Patches werden mit maxBytes 450.000 geholt und pro Projekt und Pfad zwischengespeichert.
Markdown verwendet den eigenen Parser der App, und LaTeX wird nativ dargestellt. Mermaid-Diagramme werden in einer WebView mit gebündelter mermaid.min.js und blockierten Netzwerkzugriffen dargestellt, und die JavaScript-Schnittstelle PlanToCodeDiagram, über die die Seite berichtet, wird wieder entfernt, wenn die Ansicht freigegeben wird.
HTML-Dokumente: ein Profil und ein Ursprung pro Vorschau
- Vorschau-Ursprunghttps://ptc-<preview-id>.invalid
Eine neue UUID unter der reservierten Domain .invalid, die nie aufgelöst wird, mit einem eigenen WebView-Profil ptc-html-<preview-id>. Das Schließen der Vorschau löscht Speicher, Cookies und Standortberechtigungen dieses Profils und schließt die Desktop-Freigabe.
- InterceptorshouldInterceptRequest
Streamt jedes GET an den Vorschau-Ursprung vom Desktop, egal welcher Frame es sendet, außer der Pfad enthält .., einen Backslash, einen Doppelpunkt oder NUL. Jede andere Anfrage an einen .invalid-Host oder ein unbekanntes Schema erhält 403, und https-, data- und blob-Anfragen gehen weiter an die WebView.
- Desktop-Freigabefiles.openHtmlPreview
Registriert die Vorschau-ID mit einem Stammordner: dem Projekt, wenn es das Dokument enthält, sonst dem Ordner des Dokuments. Der Desktop löst jeden Lesezugriff innerhalb dieses Ordners auf, deshalb erhält ein Pfad, der ihn verlässt, auch über einen Symlink, 403.
- LinksshouldOverrideUrlLoading
Ein Tippen auf einen http- oder https-Link geht an Android, und die Seite bleibt, wo sie ist. Links auf den Vorschau-Ursprung laden in der Vorschau, und jede andere Navigation wird verworfen.
Die App verlangt die WebView-Funktion MULTI_PROFILE und bittet um ein WebView-Update, wenn sie fehlt. Profile, die ein früherer Prozess hinterlassen hat, werden vor dem ersten neuen gelöscht, und das Schließen einer Vorschau zerstört ihre WebView.
Die WebView liest jede abgefangene Ressource über einen blockierenden InputStream auf ihrem Ressourcen-Thread, der nur den gerade gelesenen Block hält. Der Desktop löst jeden Pfad innerhalb der Freigabe erneut auf, deshalb endet auch ein Symlink, der aus ihr hinausführt, mit 403.
JavaScript und DOM-Speicher sind an. Datei- und Content-Zugriff sind aus, gemischte Inhalte sind nie erlaubt, die App verbietet unverschlüsselten Verkehr, der Cache ist deaktiviert, Safe Browsing ist an, und ein SSL-Fehler bricht das Laden ab. Es wird keine JavaScript-Schnittstelle hinzugefügt. Stirbt der Renderer-Prozess, gibt der Controller die WebView frei und bietet ein Neuladen an.
PDFs, zwischengespeicherte Vorschauen und Medienstreams
PDF-Seiten werden mit PdfRenderer auf dem IO-Dispatcher gerendert, jede Seite skaliert mit dem kleineren Wert aus 2 und 2.560 geteilt durch ihre lange Kante und in ARGB_8888 auf Weiß gezeichnet. Ein PDF-Link in der Website-Vorschau wird vom Download-Listener der WebView abgefangen und mit OkHttp unter einem Aufruf-Zeitlimit von 60 Sekunden geholt, mit Cookies aus dem CookieManager der WebView für jeden Weiterleitungsschritt. Die Browsersitzung behält die heruntergeladene Datei und ihre Scrollposition beim Vor- und Zurücknavigieren.
Dateien öffnen sich über CachedPreviewReader. Er holt die Vorschau-Metadaten mit SHA-256, Größe und Inhaltsversion, lädt in geprüften Blöcken herunter, bricht ab, wenn sich die Version mitten im Download ändert, und prüft den Digest vor der Verwendung. PreviewDiskCache ist nach Konto, Desktop, Projekt, Pfad und Darstellung geordnet, hält bis zu 512 MiB und verwirft Einträge, die 3 Tage unbenutzt waren.
- Fingerabdruckfiles.getPreviewMetadata
Bei jedem Öffnen hasht der Desktop die ganze Datei und gibt nur SHA-256, Größe, MIME-Typ und Inhaltsversion zurück. Eine Kopie im Cache zählt nur dann als Treffer, wenn der eigene Hash des Telefons übereinstimmt.
- Downloadfiles.readBinary
Blöcke von höchstens 4 MiB kommen der Reihe nach an, und der Download scheitert, wenn sich die Inhaltsversion ändert. Die fertige Datei muss dem SHA‑256 entsprechen, bevor sie die Kopie im Cache ersetzt.
- Cache512 MiB · 3 Tage
App-privat, pro Konto, Desktop, Projekt, Pfad und Darstellung. Zuerst gehen Einträge, die 3 Tage unbenutzt waren, dann oberhalb von 512 MiB die am längsten unbenutzten, nie der neueste oder ein geöffneter.
Audio und Video spielen in ExoPlayer über eine eigene Datenquelle, die Bereiche von 512 KiB in einen Cache von 128 MiB liest. Ein laufender Medienstream hält das Relay offen, nachdem die Activity gestoppt hat.
Zwei Recorder-Dienste
- Recorder-DienstReviewScreenRecorderService
Startet nach einer einmaligen Zustimmung, RECORD_AUDIO und dem Aufnahme-Dialog des Systems und nimmt weiter auf, während andere Apps benutzt werden. Er schreibt H.264 mit 5 Mbit/s und 30 fps und AAC mit 128 kbit/s, mit höchstens 1.280 Pixeln an der langen Kante.
- ÜbergabesendOrderedBroadcast
Das Ergebnis beginnt als RESULT_CANCELED, deshalb löscht der Dienst die Datei, außer der Workspace-Tab antwortet mit RESULT_OK, solange er noch Session und Projekt der Aufnahme zeigt. Hat der Upload begonnen, stoppt ihn das Verlassen des Tabs nicht mehr.
- Größengrenze100 MiB
Wird vor dem Upload geprüft, für Aufnahmen und für vom Gerät gewählte Videos. Bei den Ziel-Bitraten erreicht eine Aufnahme sie nach etwa 2 min 44 s.
- Analyse/api/llm/video/analyze
Der Server schreibt den Upload in eine temporäre Datei, sendet ihn an Gemini, unter 19 MiB inline und ab 19 MiB über die File API von Gemini, und löscht die temporäre Datei, bevor er antwortet. Die Analyse landet im Chat-Entwurf.
Das Manifest deklariert drei Typen von Vordergrunddiensten: mediaProjection und microphone für den Recorder des Review Mode, microphone für das Diktat und mediaPlayback für Medienvorschauen. Mikrofon-Hardware ist als optional markiert, deshalb blendet Google Play die App auf Geräten ohne Mikrofon nicht aus.
Das Diktat nimmt mit MediaRecorder als AAC mit 128 kbit/s und 44,1 kHz in eine .m4a-Datei auf und aktualisiert die Pegelanzeige alle 50 Millisekunden. Die Aufnahme wird als Multipart an /api/audio/transcriptions mit dem Modell openai/gpt-4o-transcribe gesendet, und der Desktop steuert den Transkriptions-Prompt über system.voiceTranscriptionContext bei, wenn er innerhalb von 750 Millisekunden antwortet.
Der Recorder-Dienst des Review Mode ruft startForeground auf, bevor er die MediaProjection erhält, mit den Typen mediaProjection und microphone ab Android 11, nur mit mediaProjection unter Android 10 und ohne Typ auf älteren Versionen. Ein VirtualDisplay mit höchstens 1.280 Pixeln an der langen Kante speist einen einzigen MediaRecorder, der die Bildschirmoberfläche und das Mikrofon aufnimmt und MPEG-4 schreibt.
Das MP4 wird als Multipart an /api/llm/video/analyze hochgeladen. Aufnahmen und vom Gerät gewählte Videos über 100 MiB werden beide schon vor dem Upload abgewiesen. Das Verlassen des Workspace-Tabs oder der Wechsel zu einer anderen Session oder einem anderen Projekt während der Aufnahme bricht die Aufnahme ab.
Push-Tokens und der desktop_online-Hinweis
Stellt Firebase ein neues Token aus, speichert der Messaging-Dienst es und holt sich einen Anspruch vom prozessweiten Push-Token-Koordinator. Die Synchronisierung liefert NoRegion, wenn keine Region gespeichert ist, sodass ein Token nie an ein geratenes Backend geht, und PUT api/devices/push-token läuft nur, solange der Besitzer des Anspruchs noch aktuell ist.
Ein desktop_online-Push wird nie angezeigt. Er schreibt einen dauerhaften Hinweis mit Desktop-ID, Relay-Instanz, Verbindungsgeneration und Presence-Revision, 5 Minuten gültig, und sendet einen Broadcast innerhalb der App. Ist die Activity sichtbar, das Netzwerk verfügbar und der Desktop der gewählte, startet die Wiederherstellung sofort, und der Hinweis wird quittiert. Andernfalls wartet der Hinweis auf das nächste onStart.
Google Play Billing und die verbundene Vorschau
PlanToCode Pro auf Android ist ein Google-Play-Produkt, com.plantocode.pro, mit wöchentlichen, monatlichen und jährlichen Basis-Abos, gekauft über Billing 9 mit ausstehenden Käufen und automatischer Wiederverbindung zum Dienst. Ein Kauf wird mit POST api/billing/google-play/subscription/verify geprüft und erst bestätigt, wenn der Server ein aktives Abonnement meldet. Die Berechtigung wird für ihren Besitzer im Arbeitsspeicher gehalten, mit einer Frist auf elapsedRealtime, deshalb wirkt sich eine Uhrumstellung nicht aus, und ein Neustart prüft erneut.
- Prüfungsystem.ping
Geht vor einer Desktop-Anfrage über das Relay, solange es kein Abonnement und keine aktive Vorschau gibt. Antwortet das Relay, dass der Desktop offline ist, scheitert die Anfrage mit dieser Antwort, und nichts startet.
- Startapi/billing/workspace-preview
Das PUT geht erst nach einem angenommenen Ping hinaus und nur, wenn sich Konto und gewählter Desktop nicht geändert haben. Der Server startet eine 10-tägige Vorschau pro Konto und beantwortet eine Wiederholung mit denselben Zeitpunkten.
- FristelapsedRealtime
Das Telefon zieht die Dauer des PUT von remainingMilliseconds ab und zählt auf der monotonen Uhr herunter, deshalb verlängert ein geändertes Datum den Zugang nicht. Nach der Frist pingt eine Anfrage den Desktop noch an und wird dann abgewiesen.
Ohne Abonnement erhält jedes Konto eine 10-tägige verbundene Vorschau, die der regionale Server startet und speichert. Das Öffnen der App liest sie nur, mit GET api/billing/workspace-preview. Antworten müssen genau die Schlüssel ihres Status tragen, und ein 401 gilt als abgelehnte Authentifizierung, die die App abmeldet.
Prüfungen vor einem Google-Play-Release
bundleRelease und assembleRelease hängen von validate16KbNativeLibraries ab, das die ELF-Programmheader jeder zusammengeführten nativen Bibliothek für arm64-v8a und x86_64 parst und scheitert, wenn ein PT_LOAD-Segment unter 16.384 Bytes ausgerichtet ist. validateGooglePlayReleaseInputs lässt den Build ohne Signatur-Eingaben scheitern, deshalb kann kein unsigniertes Bundle versehentlich hinausgehen.
Pull Requests führen testDebugUnitTest, lintDebug und compileDebugAndroidTestKotlin aus. Instrumentierte Tests, darunter die Paritätssuiten für wachsende Live-Timelines, fortlaufendes PDF-Lesen, Vorschau-Interaktion und Diktat-Steuerung, laufen auf dem verwalteten Gerät pixel2api35.