Zum Artikel springen
PlanToCodeDocsApp herunterladen

HandbuchAndroid-App

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.

Aus dem Repository-Stammverzeichnis
node scripts/integrations/android_textmate/sync-android-textmate-grammars.mjs

Die 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

Nur die App kann eine Anfrage an den Vorschau-Ursprung beantworten
Nur die App kann eine Anfrage an den Vorschau-Ursprung beantwortenEine HTML-Seite auf dem Vorschau-Ursprung sendet sechs Arten von Anfragen. Ein GET auf eine Datei dieses Ursprungs oder ein Link darauf wird über das Relay aus der Desktop-Freigabe gestreamt. Ein POST, ein Pfad mit .. oder ein anderer .invalid-Host erhält 403. Eine http-Unterressource blockieren die eigenen Regeln der WebView, während https-Unterressourcen und iframes aus dem Web laden. Jede andere Navigation wird verworfen, und ein Tippen auf einen Weblink öffnet ihn in Android.
HTML-Seitehttps://ptc-<preview-id>.invalid
GET ./app.js oder ein Link auf ./page.html
POST, .. im Pfad oder ein anderer .invalid-Host
Unterressource über http://
Unterressource oder iframe über https://
jede andere Navigation
Tippen auf einen Weblink
Prüfungen der App
eigene Regeln der WebView
Relay
in Blöcken von 4 MiB gestreamt
403
blockiert: keine gemischten Inhalte, kein Klartext
verworfen
Desktop-FreigabeProjektordner oder Ordner des Dokuments
ein Pfad, der sie verlässt, erhält 403
Das WebSafe Browsing an, SSL‑Fehler brechen ab
Androidöffnet den Link außerhalb der App
  • 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.

Unveränderte Dateien öffnen nach einer Prüfung ihres Fingerabdrucks aus dem Cache
Unveränderte Dateien öffnen nach einer Prüfung ihres Fingerabdrucks aus dem CacheEin Raster aus Netzwerkzustand und Zustand der Kopie im Cache, mit einem Balken für die Dateibytes, die bei einer Datei von 20 MiB über das Relay gehen. Eine Kopie, die dem SHA-256 des Desktops entspricht, öffnet sich in jedem Netzwerk ohne Download. Eine veraltete Kopie wird in einem nicht getakteten Netzwerk ersetzt, und in einem getakteten Netzwerk oder mit Datensparmodus öffnet sich die alte Kopie mit einem verfügbaren Update, bis Download update angetippt wird. Ohne Kopie wird die Datei in jedem Netzwerk heruntergeladen. Ist der Desktop nicht erreichbar, öffnet sich eine vorhandene Kopie ungeprüft, und ohne Kopie erscheint der Verbindungsfehler.
Kopie entspricht dem SHA-256 des Desktops
Kopie ist veraltet
keine Kopie im Cache
ungetaktetes Netz
getaktetes Netz, Datensparmodus
Desktop nicht erreichbar
öffnet die Kopie
lädt herunter und ersetzt die Kopie
lädt herunter und legt sie im Cache ab
öffnet die Kopie
öffnet die alte KopieUpdate available · cached copy
lädt trotzdem herunter
öffnet die vorhandene KopieCached copy · checked <Datum>
zeigt den Verbindungsfehler
Download update
Balken: Dateibytes über das Relay für eine Datei von 20 MiB, in Blöcken von 4 MiB
ein echter Fehler vom Desktop, etwa eine gelöschte Datei, erscheint auch, wenn eine Kopie existiert
  • 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

Eine Aufnahme im Review Mode bleibt nur bis zum Ende ihres Uploads auf dem Telefon
Eine Aufnahme im Review Mode bleibt nur bis zum Ende ihres Uploads auf dem TelefonDas Leben einer Aufnahme. Der Recorder-Dienst besitzt das MP4, solange er aufnimmt, und übergibt es per Ordered Broadcast an die App. Die App behält es nur, wenn sie mit RESULT_OK antwortet, lädt es auf den regionalen Server hoch und löscht es, wenn die Anfrage endet, während der Server seine temporäre Datei nach der Analyse löscht. Das Verlassen des Workspace-Tabs oder ein Session-Wechsel während der Aufnahme, eine gescheiterte oder unbeantwortete Übergabe und eine Datei über 100 MiB führen jeweils schon früher zum Löschen.
Recorder-DienstVordergrunddienst
AppWorkspace-Tab, dann Upload
Regionaler ServerVideoanalyse
nimmt Bildschirm und Mikrofon auf
besitzt das MP4
temporäre Datei
Workspace-Tab verlassen oder Session wechseln: abgebrochen und gelöscht
Ordered Broadcast
RESULT_OK
Stopp scheitert, Datei leer oder kein RESULT_OK: gelöscht
über 100 MiB: gelöscht
lädt das MP4 hoch
Analyse
gelöscht
gelöscht
Start
Stop
Upload fertig
  • 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.

Die 10-tägige Vorschau startet erst, wenn ein Desktop geantwortet hat
Die 10-tägige Vorschau startet erst, wenn ein Desktop geantwortet hatTelefon, regionaler Server und Desktop im Zeitverlauf. Solange der Desktop offline ist, kommt der system.ping des Telefons über das Relay mit Desktop is offline zurück, die Anfrage scheitert, und die Vorschau bleibt ungestartet. Sobald der Desktop den Ping annimmt, sendet das Telefon PUT, der Server startet die 10-tägige Vorschau, und die ursprüngliche Anfrage geht hinaus. Bis zu seiner eigenen Frist lässt das Telefon Anfragen ohne Ping durch, danach werden sie abgewiesen.
TelefonRPC-Client
Regionaler ServerRelay und Abrechnung
Desktop
nicht gestartet
10 Tage aktiv
abgelaufen
Anfragen ohne Ping
abgewiesen
Anfrage scheitert, nichts startet
system.ping
Desktop is offline
system.ping
angenommen
PUT
ursprüngliche Anfrage
Frist auf der eigenen Uhr des Telefons
Desktop offline
Desktop online
10 Tage später
  • 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.