Everything posted by Vitaly
-
DashCast — BYD Cluster Launcher & Mirror
DashCast v1.2.14DashCast v1.2.14 (build 205) — Diag → Sniffer: "Nettoyer" button restored Why Field report (23/05/2026, DL3 Seal EU): on long-lived installs the app's external storage footprint grows past 500 MB because BYD_RE_Sniffer_*.txt captures (~10–20 MB each), byd_log_*.log and byd_report_*.txt files accumulate across sessions. The user-facing cleanup button existed before the M3 redesign (wiped at commit 229059c, v0.9.88) but was never restored when the new tabbed Diag UI shipped. Only the static helper AppLogger.cleanupFiles(Context) survived (unused). What A new Nettoyer button at the right end of the action row in Diag → Sniffer, next to Exporter. Behaviour If a capture is currently active → Toast Arrête le sniffer avant de nettoyer (la capture écrit dans un fichier). and no deletion (otherwise we'd delete the file the sniffer is writing to and leak its setsid background processes). Otherwise → confirmation AlertDialog ("Nettoyer les fichiers DashCast" / "Supprime tous les logs et captures… Les clés ADB et préférences sont préservées.") → on confirm: Spawns sniffer-cleanup-thread. Calls existing AppLogger.cleanupFiles(this) which deletes: byd_log_*.log (AppLogger.saveToFile) byd_report_*.txt (SysInfoActivity) BYD_RE_Sniffer_*.txt (DiagActivity) cluster_live.png (AdbLocalClient.captureClusterDisplay) Clears the in-memory log buffer Preserved: ADB keys (adb.key / adb.pub), SharedPreferences. Walks the same dirs to compute remaining bytes (B / KB / MB auto-format). Clears mSnifferFile + PREF_SNIFFER_PATH (the just-cleared capture is no longer exportable). Posts back to UI: re-enables the button, updates the Sniffer status line with Nettoyage : N fichier(s) supprimé(s) · restant : SIZE, Toast confirms. Touched files app/build.gradle — versionCode 204→205, versionName 1.2.13→1.2.14. DiagActivity.java — btnSnifferCleanup field, bindSnifferPanel wiring, cleanupSnifferFiles() + doCleanupSnifferFiles() (~80 LoC restored from the pre-wipe pattern). include_diag_sniffer.xml — 5th button in action row. Reuses the long-existing AppLogger.cleanupFiles(Context) (no logic change there). No new permissions, no new dependencies, no new strings, no protocol bump. Behavioural contract DL3 / DL5 / DL2 — identical surface: Sniffer tab gains the Nettoyer button. Zero impact on cluster projection, mirror, IME, resize, or any other DashCast subsystem. Validation Open Diag → Sniffer → button Nettoyer visible at the right of Exporter. Start sniffer → tap Nettoyer → Toast asks to stop first, no deletion. Stop sniffer → tap Nettoyer → AlertDialog → confirm → within a few hundred ms Toast confirms N fichier(s) supprimé(s) — restant : SIZE and the status line updates. On a device with > 500 MB DashCast footprint: post-cleanup, Settings → Apps → DashCast → Storage shows < 1 MB of "Data" (only prefs + ADB keys remain). APK DashCast-v1.2.14-debug.apk (≈14.5 MB). LinksAPK download GitHub release page
-
DashCast — BYD Cluster Launcher & Mirror
DashCast v1.2.13DashCast v1.2.13 (build 204) — DL5 per-app resize fix Pre-release for the DL5 testeur (Android API 32 cluster fission topology). DL3 Seal EU production and DL2 are byte-for-byte identical to v1.2.12 except for the dormant code paths described below. Symptom (field log BYD_RE_Sniffer_20260523_141144.txt, 15.3 MB) The user moved the W/H sliders for ru.yandex.yandexmaps repeatedly and tapped Apply (40+ times spread over 14:12:16–14:13:14). Each tap produced: I BYDApp: Applied custom resize 323/193 for ru.yandex.yandexmaps D AdbLocalClient: executeShellWithResult: wm overscan 323,193,323,193 -d 3 -> Unknown command: overscan …and zero ClusterService: findRunningTaskId … or resizeActiveTask … OK log lines. The Yandex window on the cluster never resized. Root causes Bug A — wm overscan removed in API 30+ wm overscan W,H,W,H -d N was removed from frameworks/base/services/core/java/com/android/server/wm/WindowManagerShellCommand.java in API 30. DL5 is API 32, so every shell invocation returns Unknown command: overscan. ClusterService.applyClusterFreeformBounds had already been gated by isDiLink5Safe in a prior release, but MainActivity.btnResizeApply and MainActivity.autoApplyInsetsIfNeeded still issued the dead command. Bug B — ActivityManager.getRunningTasks only returns the caller's own task on API 21+ ClusterService.findRunningTaskId(packageName) enumerated ActivityManager.getRunningTasks(50) looking for the matching package. On API 21+ Android restricts this API: non-system apps see only their own task. DashCast is not system, so the loop never finds Yandex → returns -1 → resizeActiveTask(-1, …) silently returned at if (taskId <= 0) return; → no log line, no resize. Display 3 vs display 2 — no action needed The cluster app's task lives on displayId=3 (1920×1080 shadow framebuffer managed by containerservice). The composed face users see is on displayId=2 (1920×720, projected by the XDJA fission compositor). IActivityTaskManager.resizeTask(taskId, bounds, RESIZE_MODE_FORCED) operates in the task's display coord system (display 3, 1920×1080), and the compositor handles the projection to display 2 with automatic scaling at composition time. Bounds were already correctly computed (Rect(215, 164 - 1705, 916) display=3 1920×1080 in the same log). The only thing missing was a valid taskId. The display-3→2 override is only required for the touch path (MotionEvent.setDisplayId(2), v1.2.7) because the InputDispatcher routes events from the composed face, not for resize. Fixes 1. ClusterService.findRunningTaskId — daemon dumpsys activity recents fallback Path 1 (kept): ActivityManager.getRunningTasks(50) — works on legacy ROMs / DL3 where DashCast may still have GET_TASKS. Path 2 (new): if BetaProxyClient.isConnected(), call BetaProxyClient.runShell("dumpsys activity recents"). The proxy daemon runs uid 2000 (same uid as adb shell) and has SHELL privileges, so dumpsys returns the full recent-tasks table cross-package. Parse with a fast-path regex Task\{[^}]*#(\d+)[^}]*\bA=<pkg>\b (matches * Recent #0: Task{xxxxxxx #88 type=standard A=ru.yandex.yandexmaps U=0 ...) and a fallback block-split scanner that also accepts realActivity=<pkg>/... or cmp=<pkg>/... within the same * Recent #N: block (covers BYD ROM variants). Every path emits a discrete log line: findRunningTaskId <pkg> → taskId=N (via AM) findRunningTaskId <pkg> → taskId=N (via daemon dumpsys recents) findRunningTaskId <pkg> — not found in dumpsys recents (out.length=N) findRunningTaskId <pkg> — daemon not connected; cannot fallback to dumpsys The parser is a static helper parseTaskIdFromDumpsysRecents(String dump, String pkg) so it's unit-testable in isolation. 2. ClusterService.resizeActiveTask — log on invalid taskId The silent if (taskId <= 0) return; now emits: W ClusterService: resizeActiveTask: taskId<=0 for pkg=<pkg> — cannot resize (lookup via AM + daemon dumpsys both failed) so a residual failure surfaces in the log instead of vanishing. 3. MainActivity — skip wm overscan on DL5 Both btnResizeApply.onClick and autoApplyInsetsIfNeeded now wrap the wm overscan call with: if (AdbLocalClient.isDiLink5Safe(MainActivity.this)) { AppLogger.d(TAG, "Apply resize DL5: skipping wm overscan (cmd removed in API 30+) — resizeTask handles it"); } else { ShellGateway.execShell(MainActivity.this, "wm overscan " + w + "," + h + "," + w + "," + h + " -d " + clusterId); } Mirrors the existing skip in ClusterService.applyClusterFreeformBounds. DL3 (API 29) still sends wm overscan -d 1 as before. Behavioural contract DL3 Seal EU production — findRunningTaskId path 1 succeeds (path 2 never reached); wm overscan -d 1 still sent on DL3 (API 29 accepts it). Byte-for-byte identical to v1.2.12. DL5 testeur — Apply now finds Yandex's taskId via the daemon dumpsys, IActivityTaskManager.resizeTask(taskId, bounds, RESIZE_MODE_FORCED) applies, the XDJA compositor reflects the new bounds on the composed display-2 face → visible resize. DL2 — no cluster path, neither code change ever fires. Touched files app/build.gradle — versionCode 203→204, versionName 1.2.12→1.2.13 ClusterService.java — findRunningTaskId rewritten (~50 LoC), new static parseTaskIdFromDumpsysRecents (~40 LoC), resizeActiveTask early-return logs (~5 LoC) MainActivity.java — both wm overscan call sites gated by isDiLink5Safe (~8 LoC each) No new permissions, no new dependencies, no new strings, no protocol bump (still PROTOCOL_VERSION=2). Validation plan DL5 testeur — install v1.2.13 over v1.2.12 (no uninstall, versionCode bumped). Start cluster projection, launch Yandex Maps on cluster, open the Adjust panel, move W/H sliders, tap Apply. Expected: Yandex window on cluster resizes visibly within the same frame. Logcat sequence: I BYDApp: Applied custom resize W/H for ru.yandex.yandexmaps D ClusterService: findRunningTaskId ru.yandex.yandexmaps → taskId=NN (via daemon dumpsys recents) I ClusterService: resizeActiveTask ru.yandex.yandexmaps Rect(...) OK No more Unknown command: overscan lines. Auto-apply on launch — restart cluster projection, observe autoApplyInsetsIfNeeded (500 ms post-launch) produces the same successful resizeTask sequence. DL3 Seal EU — install, navigate, resize sliders work exactly as before (path 1 still succeeds). Edge case (daemon down) — WARN findRunningTaskId … daemon not connected surfaces, resize quietly fails with the new taskId<=0 WARN. Both diagnostics visible, no crash. DL2 — install, navigate, no cluster path triggered. APK DashCast-v1.2.13-debug.apk (≈14.5 MB). LinksAPK download GitHub release page
-
DashCast — BYD Cluster Launcher & Mirror
DashCast v1.2.12v1.2.12 (build 203) — DL5 KeyboardBridge: a11y ACTION_SET_TEXT pivot (replaces KeyEvent injection) What changed Field log of DL5 testeur (log/BYD_RE_Sniffer_20260523_140358.txt, captured after v1.2.11 install — logcat confirms "package":"com.byd.dashcast","versionCode":"202") showed v1.2.11's 1×1 invisible EditText was never served by the system InputMethodManager: W InputMethodManager: Ignoring showSoftInput() as view=android.widget.EditText{… VFED..CL. .F....ID 0,0-1,1} is not served. Consequences observed: IME window pops up (because SOFT_INPUT_STATE_ALWAYS_VISIBLE is independent of the served view) — user sees the Yandex keyboard. But no InputConnection is bound to the EditText → TextWatcher.onTextChanged never fires → zero injectKey FIRST OK traces from MirrorDaemon over the 30-min capture window. The whole v1.2.8/v1.2.11 KeyEvent forwarding chain (KeyCharacterMap → KeyEvent → ClusterInputForwarder.injectKeyEvent → MirrorDaemon → InputManager.injectInputEvent) is never exercised. Architectural pivot v1.2.12 replaces the entire KeyEvent injection chain with the standard a11y subsystem path: node.performAction( AccessibilityNodeInfo.ACTION_SET_TEXT, Bundle{ ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE: text } ); The a11y subsystem is cross-display by construction (TalkBack uses the same mechanism to read out content from any display) and is immune to per-display IME isolation or the DL5 cluster fission compositor (composed face on displayId=2, focused app window on mDisplayId=3 1×1 shadow framebuffer). This sidesteps the unverified KeyEvent.setDisplayId(2) cross-display routing that v1.2.11 relied on. Implementation ClusterImeWatcherService (existing AccessibilityService gains static helpers + cluster window scanner): static volatile ClusterImeWatcherService sInstance published in onServiceConnected, cleared in onDestroy. onServiceConnected: info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS so getWindows() returns cluster windows. static boolean setTextOnCluster(CharSequence text) — finds the focused editable on a cluster window via findClusterFocusedEditable(), calls node.performAction(ACTION_SET_TEXT, …), logs WARN on refusal, recycles the node in finally. static boolean performImeEnterOnCluster() — same node discovery, then AccessibilityAction.ACTION_IME_ENTER.getId() on API 30+ or ACTION_CLICK fallback. findClusterFocusedEditable() walks getWindows(), prefers windows whose getDisplayId() > 0, falls back to first editable on any window. KeyboardBridgeActivity rewritten (~410 LoC → 200 LoC): Window 220 dp × 48 dp (was 1×1, not served), Gravity.BOTTOM | Gravity.END, transparent, dim 0. EditText with TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_NO_SUGGESTIONS, IME_ACTION_DONE | IME_FLAG_NO_EXTRACT_UI, single-line, transparent text/hint, no cursor, no padding. showSoftInput is now called from onWindowFocusChanged(true) instead of onCreate.post(…) — fixes the race that produced the "is not served" warning. TextWatcher.onTextChanged body reduced to a single line: ClusterImeWatcherService.setTextOnCluster(s.toString()). OnEditorActionListener → performImeEnterOnCluster(); on success clear the local field. Removed: KeyCharacterMap mKcm, CharSequence mLastText, boolean mSuppressWatcher, forwardKeyCode, forwardKeyEvent, mapAsciiToKeyCode, all ClusterInputForwarder references. accessibility_ime_watcher.xml: added android:accessibilityFlags="flagRetrieveInteractiveWindows" (mirror of the runtime flag). Daemon paths preserved (dormant): MirrorDaemon.injectKey + sSetDisplayIdKey reflection + ClusterInputForwarder.injectKeyEvent are NOT removed — they remain available for any future code path that needs raw KeyEvent injection. The bridge simply no longer calls them. Behavioural contract DL3 Seal EU production: ClusterImeWatcherService.onAccessibilityEvent early-returns on !Platform.isDiLink5, the bridge button is View.GONE, neither helper is ever invoked — zero behavioural change. DL5 testeur: user taps a cluster EditText → a11y service detects focus on displayId > 0, launches bridge → bridge appears as a 220×48dp invisible window → IMM serves the EditText → IME pops up → user types → each onTextChanged calls setTextOnCluster which performs ACTION_SET_TEXT on the cluster Yandex editable → text appears character-by-character on the cluster Yandex search field → tap IME's Enter → performImeEnterOnCluster fires ACTION_IME_ENTER → Yandex executes the search. DL2: completely unaffected (a11y service inert, no cluster path). Validation plan DL5 testeur — install v1.2.12 over v1.2.11 (no uninstall, versionCode 202→203 bumped). Start cluster projection, launch Yandex Maps on the cluster, tap the search field. Expected: IME pops up at the bottom of the head unit, no Ignoring showSoftInput() … is not served warning in logcat, each typed character causes the Yandex search field on the cluster to update, IME Enter triggers search. Logcat — search for ClusterImeWatcher tag. Look for setTextOnCluster ACTION_SET_TEXT refused WARN (rare, possible if Yandex's EditText doesn't expose ACTION_SET_TEXT). Look for findClusterFocusedEditable scan failed ERROR (would indicate FLAG_RETRIEVE_INTERACTIVE_WINDOWS propagation issue on this ROM). DL3 Seal EU — install, navigate UI: ⌨ button remains GONE, no log lines from ClusterImeWatcher (early-return via !isDiLink5). DL2 — install, navigate UI: zero impact. Touched files app/build.gradle (versionCode 202→203, versionName 1.2.11→1.2.12) app/src/main/java/com/byd/dashcast/KeyboardBridgeActivity.java (rewritten) app/src/main/java/com/byd/dashcast/ime/ClusterImeWatcherService.java (+sInstance, +3 methods, +FLAG_RETRIEVE_INTERACTIVE_WINDOWS) app/src/main/res/xml/accessibility_ime_watcher.xml (+flagRetrieveInteractiveWindows) CHANGELOG.md No new permissions, no new dependencies, no new strings, no protocol bump (PROTOCOL_VERSION=2 unchanged). LinksAPK download GitHub release page
-
BYD Titanium 7
Firmware update 34.1.35.2602261.1Firmware version34.1.35.2602261.1ModelsBYD Titanium 7Update contentUnknownChipset34 chipsetRegionChineseDetailsDi5.1_34.1.35.2602261.1 🇷🇺 Китайская прошивка на 34 чипсет. 🇺🇸 Chinese firmware for 34 chipset. 🇺🇿 34-chipset uchun xitoy proshivka LinksLink Update instruction:
-
DashCast — BYD Cluster Launcher & Mirror
DashCast v1.2.11DashCast v1.2.11 — build 202 (pre-release) Target branch: beta/1.2.0-dilink5 Platforms affected: DiLink 5 (testeur) only — fix is gated by Platform.isDiLink5. DL3 / DL2 unaffected. Protocol: PROTOCOL_VERSION=2 unchanged APK: DashCast-v1.2.11-debug.apk (14 MB, versionCode 202, versionName 1.2.11) versionCode 201 → 202 so the BYD AUTO OTA installer recognises this as an update over v1.2.10 — no uninstall required. Why this release Field report (May 23, 2026) on DL5 testeur with v1.2.10 installed, system locale = Russian, Yandex Maps active on the cluster. Two coupled symptoms reported from a single screenshot: A French opaque banner appeared at the top of the head-unit screen the moment the keyboard bridge was triggered (manually or via the a11y service). Title Clavier déporté, hint Tapez ici : chaque caractère est envoyé au champ actif du cluster., three buttons Entrée / Retour / Fermer. The banner was in French regardless of the system locale and intrusively covered part of the live cluster mirror. Characters typed on the system IME never reached the Yandex search field on the cluster. The keyboard popped up, the user typed, but nothing appeared on the cluster side. The user explicitly hypothesised this was the same root cause as the May 22 touch-injection bug (v1.2.7 fix: MotionEvent.setDisplayId(2)). They were right — same pattern, applied to KeyEvent this time. Root cause #1 — Visible chrome (always FR, intrusive) KeyboardBridgeActivity was authored in v1.2.8 with a full LinearLayout: title TextView + hint TextView + visible EditText + 3-button row, semi-opaque dark background 0xCC202020, MATCH_PARENT × WRAP_CONTENT pinned at the top of display 0. The visible chrome was purely cosmetic — the system IME pops up regardless of EditText visibility — but the strings were never translated to the 11 supported locales and the overlay was always present, covering part of the cluster mirror. Fix — invisible 1×1 px bridge window KeyboardBridgeActivity.onCreate rewritten: Removed the entire LinearLayout + title + hint + 3-button row. Window is now 1×1 px pinned BOTTOM | END, transparent background (android.R.color.transparent), dim 0f. Sole content view is the focused EditText, stripped of every visible property: setBackground(null) transparent text & hint colours (0x00000000) setCursorVisible(false) zero padding The IME still pops up automatically thanks to SOFT_INPUT_STATE_ALWAYS_VISIBLE + mImm.showSoftInput posted on the EditText. The 1 px footprint means the rest of display 0 (DashCast UI, cluster mirror tile) stays interactive — touches outside that 1 px fall through to the windows below. Enter forwarding preserved via the existing OnEditorActionListener (IME's Done/Search/Send button → KEYCODE_ENTER injected to cluster). Back key naturally closes the activity (Android default onBackPressed). Unused imports (View, Button, LinearLayout) and the unused dp(int) helper removed. The translated keyboard_bridge_* strings remain in values/strings.xml for the activity label in the manifest (android:label=@string/keyboard_bridge_title, but excludeFromRecents=true so the label is never user-visible). Root cause #2 — Keys never reach the cluster MirrorDaemon.injectKey(KeyEvent) (uid 2000 daemon) called InputManager.injectInputEvent(kev, ASYNC) without first calling KeyEvent.setDisplayId(sClusterDisplayId). Without the displayId override, the system InputDispatcher routes the key to the globally focused window — and on multi-display Android 12, focus is per-display: our own KeyboardBridgeActivity on display 0 owns the head-unit focus and swallows every injected key. The cluster app on display 2 never sees a single KeyEvent. This is the exact same root cause as the May 22 touch-injection bug (MotionEvent wasn't routed to display 2). v1.2.7 fixed touch by adding MotionEvent.setDisplayId(clusterId). The symmetric KeyEvent.setDisplayId(clusterId) call was simply never added when the keyboard bridge shipped in v1.2.8. Fix — KeyEvent.setDisplayId(clusterId) in both injection paths daemon/MirrorDaemon.java (primary daemon-uid path): New static sSetDisplayIdKey Method cached via KeyEvent.class.getDeclaredMethod("setDisplayId", int.class) alongside the existing sSetDisplayId for MotionEvent in initInputManager(). injectKey(KeyEvent) now calls sSetDisplayIdKey.invoke(kev, sClusterDisplayId) (with inner try/catch so a reflection failure still attempts the bare inject) BEFORE InputManager.injectInputEvent. First-event log line upgraded: injectKey FIRST OK displayId=N setDisplayIdAvail=true/false keyCode=K action=A (mirror of the existing motion log) so the next field capture exposes whether the override took effect. dashboard/ClusterInputForwarder.java (direct app-uid fallback, used when daemon Binder is null): New instance mSetDisplayIdKeyMethod cached in the constructor. Applied in the direct-fallback path of injectKeyEvent(KeyEvent) immediately before InputManager.injectInputEvent. Both paths now route keys to the cluster display id (typically 2 on DL5 — same as touch). Behavioural contract DL3 Seal EU (production): the ⌨ button is hidden (Platform.isDiLink5(this) ? VISIBLE : GONE), the a11y service short-circuits on !isDiLink5, the bridge is never launched → zero observable change. DL5 testeur: Tap the ⌨ button or focus an EditText on the cluster (with the a11y service enabled) → no visible overlay, IME pops up at the bottom of display 0. Each typed character → KeyCharacterMap → KeyEvent[] → setDisplayId(clusterId) → daemon injectKey → cluster app's focused EditText receives the keys natively (no app-level integration needed). IME's Done/Search/Send button → KEYCODE_ENTER injected to cluster (e.g. triggers Yandex Maps search). DL2: completely unaffected (no cluster, ⌨ button hidden, a11y service inert). Touched files File Change app/build.gradle versionCode 201 → 202, versionName "1.2.10" → "1.2.11" app/src/main/java/com/byd/dashcast/daemon/MirrorDaemon.java +sSetDisplayIdKey field, +KeyEvent.class.getDeclaredMethod("setDisplayId") lookup in initInputManager, +setDisplayIdKey.invoke(kev, sClusterDisplayId) in injectKey with inner try/catch, +upgraded first-event log (~15 LoC net). app/src/main/java/com/byd/dashcast/dashboard/ClusterInputForwarder.java +mSetDisplayIdKeyMethod field, cached in ctor, applied in direct-fallback path (~10 LoC). app/src/main/java/com/byd/dashcast/KeyboardBridgeActivity.java Entire onCreate body replaced (~70 LoC → ~50 LoC). All visible chrome stripped, 1×1 px transparent window, invisible focused EditText. Unused imports + dp(int) helper removed. CHANGELOG.md v1.2.11 build 202 entry. Net stats: 5 files changed, 55 insertions, 76 deletions (the chrome removal is bigger than the displayId-routing addition). No new permissions. No new dependencies. No new strings. No new layouts. No protocol bump (still PROTOCOL_VERSION=2). Validation plan (in-car checks) (a) DL5 testeur — invisible bridge Install v1.2.11 over v1.2.10 (no uninstall, versionCode bumped). Start cluster projection, launch Yandex Maps on the cluster. Tap the search field on the cluster (with a11y service enabled) or tap the ⌨ button manually. Expected: NO French overlay at the top, only the IME keyboard at the bottom of display 0. The cluster mirror remains fully visible. (b) DL5 testeur — characters reach the cluster Continuing from (a), type any text on the IME. Expected: every character appears immediately in the Yandex search field on the cluster. Tap IME's Done/Search button. Expected: KEYCODE_ENTER reaches Yandex → search fires. (c) Logcat smoking gun Run adb logcat | grep MirrorDaemon. Type the first character. Expected line: injectKey FIRST OK displayId=2 setDisplayIdAvail=true keyCode=29 action=0 displayId=2 → the cluster display id was applied. setDisplayIdAvail=true → reflection succeeded on this ROM. If setDisplayIdAvail=false → reflection failed, falls back to default routing (cluster won't receive — escalate to alternative routing). (d) DL3 Seal EU regression Install v1.2.11 on DL3 production. Navigate UI. Expected: ⌨ button remains GONE, no log lines from KeyboardBridge, no post-!isDiLink5 log lines from ClusterImeWatcherService. All other features behave identically to v1.2.10. (e) DL2 regression Install v1.2.11 on DL2. Expected: zero impact (no cluster path triggered). Install Direct sideload (BYD file manager → tap APK): DashCast-v1.2.11-debug.apk Or via DashCast's built-in OTA updater once GitHub picks up this release. Branch & tag Branch: beta/1.2.0-dilink5 Commit: ede59ee Tag: v1.2.11 Previous: v1.2.10 (build 201, May 23, 2026 — DL5 IME banner one-click activation + i18n) Next planned: v1.3.0 — full ProxyDaemon migration (see docs/PLAN_v1.3.x_FULL_PROXY.md) LinksAPK download GitHub release page
-
DashCast — BYD Cluster Launcher & Mirror
DashCast v1.2.10DashCast v1.2.10 — build 201 (pre-release) Target branch: beta/1.2.0-dilink5 Platforms affected: DiLink 5 (testeur) — banner visibility / fix is DL5-only. DL3 / DL2 unaffected. Protocol: PROTOCOL_VERSION=2 unchanged APK: DashCast-v1.2.10-debug.apk (14.5 MB, versionCode 201, versionName 1.2.10) Increment versionCode 200 → 201 so the BYD AUTO OTA installer recognises this as an update over v1.2.9 build 200 — no uninstall required. Why this release Field report (May 23, 2026) on DL5 testeur, immediately after the v1.2.9 install, surfaced two issues with the IME accessibility onboarding banner introduced in v1.2.8: The banner was hardcoded in French — non-FR system locales (German, English, Italian, Spanish, etc.) fell back to the French strings. The "Enable" button appeared to do nothing — the user tapped it multiple times, no visible reaction. Both root causes identified and fixed in this build. Root cause #1 — Missing translations The 8 IME-related string keys introduced in v1.2.8 (a11y_ime_watcher_label, _summary, _description, ime_banner_title, _body, _btn_enable, _btn_later, _btn_dismiss, _toast_enabled) only existed in res/values/strings.xml (the default = French). None of the 11 sibling locale folders (values-en, values-de, values-es, values-it, values-ar, values-be, values-kk, values-ru, values-tr, values-uk, values-uz) had these keys, so Android's resource resolver fell back to the default French strings on any non-FR system locale. Fix — 10 strings × 11 locales = 110 new translations Hand-crafted, locale-appropriate translations of every IME banner string for all 11 supported locales: Locale Status values-en/ (English) +10 strings values-de/ (German) +10 strings values-es/ (Spanish) +10 strings values-it/ (Italian) +10 strings values-ru/ (Russian) +10 strings values-uk/ (Ukrainian) +10 strings values-be/ (Belarusian) +10 strings values-tr/ (Turkish) +10 strings values-ar/ (Arabic) +10 strings values-kk/ (Kazakh) +10 strings values-uz/ (Uzbek) +10 strings values/ (FR default) +1 new key (ime_banner_toast_cannot_open_settings) Every translation uses locale-appropriate keyboard / cluster terminology (e.g. "Cluster auto-keyboard" in English, "Cluster-Auto-Tastatur" in German, "Auto-pernetaqta" in Kazakh, etc.). Root cause #2 — "Enable" button silently failing The v1.2.8 button handler fired a single intent: Intent i = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS); i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(i); // caught Throwable, logged only — no toast, no fallback, no visible feedback On BYD AUTO ROM (heavily customised Android 10 on DL3 and Android 12 on DL5), the standard AOSP Settings.ACTION_ACCESSIBILITY_SETTINGS action is not advertised by BYD's custom Settings.apk. The intent silently failed to resolve, the Throwable was caught, only an AppLogger line was written — and the user saw nothing happen. Tapping multiple times produced no effect because each tap hit the same dead path. Fix — one-click activation via daemon shell + three-tier Settings fallback The "Enable" button now bypasses the Settings UI entirely and uses the proxy daemon's shell (uid=2000, same uid as adb shell) to write directly to Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES via the settings binary. Primary path — enableImeA11yServiceOneClick(): COMP='com.byd.dashcast/com.byd.dashcast.ime.ClusterImeWatcherService' CURRENT=$(settings get secure enabled_accessibility_services 2>/dev/null) if [ "$CURRENT" = "null" ] || [ -z "$CURRENT" ]; then NEW="$COMP" elif echo "$CURRENT" | grep -q "$COMP"; then NEW="$CURRENT" else NEW="$CURRENT:$COMP" fi settings put secure enabled_accessibility_services "$NEW" settings put secure accessibility_enabled 1 echo OUT=$(settings get secure enabled_accessibility_services) The shell is posted through ShellGateway.execShellWithResult which already routes through either the typed proxy daemon or the legacy AdbLocalClient fallback chain. On onSuccess, the OS state is re-read via ClusterImeWatcherService.isEnabled(this) (we trust the OS, not the shell echo). If true: Banner card hidden. Toast: localised ime_banner_toast_enabled (e.g. "Auto-keyboard active ✓" in English, "Auto-Tastatur aktiv ✓" in German). AppLogger info line for forensics. Fallback path — openA11ySettingsFallback(): If the shell route fails (no daemon, no legacy fallback, ROM blocks settings put, or the shell succeeds but the OS still reports the service as disabled — rare SELinux edge case), three intents are tried in sequence, each wrapped in its own Throwable catch: Standard AOSP — Settings.ACTION_ACCESSIBILITY_SETTINGS (works on stock AOSP). Direct ComponentName — com.android.settings/.Settings$AccessibilitySettingsActivity (BYD ROM may not advertise the standard action but still ship the activity). Generic Settings — Settings.ACTION_SETTINGS (last resort — user manually navigates to Accessibility). If all three fail, a localised toast ime_banner_toast_cannot_open_settings is shown ("Cannot open Settings — please enable the accessibility service manually") so the user finally gets visible feedback instead of the silent failure of v1.2.8 / v1.2.9. Button is always re-enabled on every code path so a failure never leaves it stuck disabled (the user can tap again). Behavioural contract DL3 (Seal EU production): banner is never shown (Platform.isDiLink5(this) == false), the new shell helper and fallback chain are never invoked → zero behavioural change. DL5 (testeur): Tap "Enable/Activate/Aktivieren/Attiva/etc." → if proxy daemon is reachable → service is enabled in <500 ms with no Settings trip required → banner disappears → localised toast confirms. If daemon down → falls back transparently to the Settings UI chain → user manually toggles the service. If Settings UI also blocked → localised toast surfaces the failure (no more silent dead button). DL2: completely unaffected (no cluster, banner is DL5-gated). Touched files File Change app/build.gradle versionCode 200 → 201, versionName "1.2.9" → "1.2.10" app/src/main/java/com/byd/dashcast/MainActivity.java Enable click handler now calls one-click helper (~5 LoC). New enableImeA11yServiceOneClick(card, btnEnable) (~70 LoC). New openA11ySettingsFallback() three-tier fallback (~40 LoC). app/src/main/res/values/strings.xml +1 new key ime_banner_toast_cannot_open_settings app/src/main/res/values-en/strings.xml +10 IME banner strings app/src/main/res/values-de/strings.xml +10 IME banner strings app/src/main/res/values-es/strings.xml +10 IME banner strings app/src/main/res/values-it/strings.xml +10 IME banner strings app/src/main/res/values-ru/strings.xml +10 IME banner strings app/src/main/res/values-uk/strings.xml +10 IME banner strings app/src/main/res/values-be/strings.xml +10 IME banner strings app/src/main/res/values-tr/strings.xml +10 IME banner strings app/src/main/res/values-ar/strings.xml +10 IME banner strings app/src/main/res/values-kk/strings.xml +10 IME banner strings app/src/main/res/values-uz/strings.xml +10 IME banner strings CHANGELOG.md v1.2.10 build 201 entry Net stats: 15 files changed, 245 insertions, 10 deletions. No new permissions. No new dependencies. No new layouts. No protocol bump (still PROTOCOL_VERSION=2). Validation plan (suggested in-car checks) (a) DL5 testeur — translation Install v1.2.10 over v1.2.9 (no uninstall required). System Settings → Language → switch to English (or German / Spanish / Italian / etc.). Open DashCast → banner appears at the top. Expected: banner title, body, and buttons are in the selected language (no French fallback). (b) DL5 testeur — one-click activation (primary path) Tap the "Enable" button (or its localised label). Expected within ~500 ms: Localised toast "Auto-keyboard active ✓" appears. Banner disappears. Go to Settings → Accessibility → confirm "Cluster auto-keyboard (DashCast)" is checked. (c) DL5 testeur — end-to-end IME bridge Start cluster projection → launch any cluster app with a text field (e.g. Yandex Maps search). Tap the text field on the cluster. Expected: KeyboardBridgeActivity auto-launches on the head unit ~100 ms later → typing forwards to the cluster. (d) Fallback path If the proxy daemon happens to be unavailable (rare, post-bootstrap), tap "Enable". Expected: Settings → Accessibility opens directly (no error visible to user, transparent fallback). User manually flips the toggle, returns to DashCast → toast confirms + banner disappears. (e) DL3 / DL2 regression check Install v1.2.10 on DL3 Seal EU / DL2. Expected: banner never shown. All other features behave identically to v1.2.9 (the new code path is never invoked). Install Direct sideload (BYD file manager → tap APK): DashCast-v1.2.10-debug.apk Or via DashCast's built-in OTA updater (Settings → Check for updates) once GitHub picks up this release. Branch & tag Branch: beta/1.2.0-dilink5 Commit: 20b8a49 Tag: v1.2.10 Previous: v1.2.9 (build 200, May 23, 2026 — DL3/DL5 Kill race + Stop Projection ghost resurrection fixes) Next planned: v1.3.0 — full ProxyDaemon migration (see docs/PLAN_v1.3.x_FULL_PROXY.md) LinksAPK download GitHub release page
-
DashCast — BYD Cluster Launcher & Mirror
DashCast v1.2.9DashCast v1.2.9 — build 200 (pre-release) Target branch: beta/1.2.0-dilink5 Platforms validated: DiLink 3 (Seal EU production reference), DiLink 5 (testeur — same code path) Protocol: PROTOCOL_VERSION=2 unchanged APK: DashCast-v1.2.9-debug.apk (14.5 MB, versionCode 200, versionName 1.2.9) Increment versionCode 199 → 200 so the BYD AUTO OTA installer recognizes this as an update over v1.2.8 build 199 — no uninstall required. Why this release Field report (May 23, 2026) on DL3 Seal EU surfaced two distinct user-visible symptoms that share the same architectural root cause: BYD AUTO ROM's am force-stop / IActivityManager.forceStopPackage kill the process but do not remove the corresponding TaskRecord from the Recents stack. The existing dumpsys-based cleanup fallback (dumpsys activity recents | grep | sed → am task remove → TaskRemover app_process) silently fails on this ROM (uid 2000 lacks REMOVE_TASKS, am task remove not always available). The same code paths are shared by DL5, so the fixes apply transparently to both platforms. Bug 1 — Long-press "Kill" leaves a ghost Recents tile Symptom: long-press any app currently rendering on the cluster → tap Kill → the process dies (cluster goes blank) BUT the app's tile remains in the head-unit Recents. Tapping it relaunches the activity. Root cause: in MainActivity.doKillApp, two async operations were fired in close succession on different threads: mClusterService.moveTaskToDisplay(pkg, 0, null) — move the cluster-active task back to display 0. AdbLocalClient.forceStopApp(...) — force-stop the package, which internally runs dumpsys activity recents | grep <pkg> | sed → am task remove $taskId to purge the Recents entry. forceStopApp's dumpsys scan could parse the recents stack during the move-to-display-0, see a stale or already-moved TaskId, and am task remove would target the wrong (or no longer existing) task — leaving the real TaskRecord orphaned in Recents. Fix (a) — serialize move → forceStop via LaunchCallback ClusterService.moveTaskToDisplay(pkg, displayId, callback) already exposed LaunchCallback.onResult(boolean) fired on the main thread (added in v1.1.x). doKillApp now: Extracts the forceStopApp callback as a final local killCallback. Wraps the cluster-app branch's moveTaskToDisplay(pkg, 0, ...) call with a LaunchCallback whose onResult fires mSessionClusterPackages.remove + persistSessionClusterPackages + AdbLocalClient.forceStopApp(this, pkg, killCallback). The non-cluster branch (else) is structurally unchanged but routes through the same extracted killCallback. Result: the dumpsys activity recents scan inside forceStopApp is now guaranteed to run after the move-to-display-0 has fully completed, eliminating the race window. Bug 2 — Stop Projection relaunches the cluster app fullscreen on the head unit Symptom: start cluster projection → launch Waze (or any app) on the cluster → tap Stop Projection. The cluster is correctly restored to the BYD dashboard, but ~1 second later Waze pops up fullscreen on display 0, covering the BYD launcher. Root cause: in MainActivity.restoreBydDashboard (TEST 10 path) and originCluster, the call order was: moveSessionAppsToMainDisplay() — iterates mSessionClusterPackages and moves every cluster-launched app (including the currently-active capturedClusterPkg) to display 0 as a foreground task. AdbLocalClient.restoreBydOnCluster(this, capturedClusterPkg, ...) — force-stops capturedClusterPkg, then fires sendInfo(18) + sendInfo(0) to restore the Qt cluster surface. Step 1 brings the active cluster app to display 0 as a foreground task. Step 2 force-stops it — kills the process, but the TaskRecord on display 0 survives (Bug 1 root cause applies here too). Then sendInfo(18)+sendInfo(0) triggers the Qt cluster surface restoration, which appears to fire a RESUME_TOP_ACTIVITY ripple effect on display 0 that picks up the orphan TaskRecord and resurrects the activity. Fix (b) — invert order: remove capturedClusterPkg from session set BEFORE moveSessionAppsToMainDisplay() Both restoreBydDashboard and originCluster now do: if (capturedClusterPkg != null) { mSessionClusterPackages.remove(capturedClusterPkg); persistSessionClusterPackages(); } moveSessionAppsToMainDisplay(); AdbLocalClient.restoreBydOnCluster(this, capturedClusterPkg, ...); Now moveSessionAppsToMainDisplay() iterates a set that no longer contains the active cluster pkg — it stays on display 1 (cluster). restoreBydOnCluster (unchanged) then force-stops it in place on display 1 just before sendInfo(18). No orphan TaskRecord ever lands on display 0, no resurrection path. Other session apps (split-mode side dashboards) are still moved to display 0 as before. Fix (c) — verifyForceStop active escalation (defense in depth) v1.2.8's AdbLocalClient.verifyForceStop only logged a WARN when BetaProxyClient.getPidsByPackage(pkg) reported surviving PIDs after the typed IActivityManager.forceStopPackage returned OK. Build 200 actively escalates: surviving PIDs → BetaProxyClient.runShell("kill -9 <pid1 pid2 ...>") via the daemon (uid 2000 owns the same uid as the target app, so the kill is permitted) → sleep 200 ms → re-query PIDs → log the final state. The whole escalation is wrapped in an inner try/catch so a daemon glitch can never break the teardown sequence. This also benefits restoreBydOnCluster / restoreOriginCluster (typed paths call verifyForceStop) — if the cluster pkg somehow survives the in-place force-stop in Fix (b), the kill -9 escalation finishes the job before sendInfo(18) fires. Known limit (roadmap) This v1.2.9 patch fixes the process-survival and race-window sides of both bugs, but it does not purge the TaskRecord itself from Recents — architectural limitation of the dumpsys + shell pipeline on BYD AUTO ROM. Practical impact: Bug 1 (Kill): the ghost Recents tile may still appear briefly on DL3. Tapping it now relaunches a fresh instance instead of resurrecting state on display 1, but the visual tile may linger. Bug 2 (Stop Projection): fully resolved by Fix (b). No Waze flash on the head unit. Robust fix scheduled for v1.3.x A new typed verb TXN_REMOVE_TASKS_BY_PACKAGE will be added to the proxy daemon: Uses IActivityTaskManager.getRecentTasks() + removeTask(taskId) from the stable uid 2000 daemon context. Atomic, no shell parse, no race. Will be called from AdbLocalClient.forceStopApp (replacing the current dumpsys + sed + am task remove + TaskRemover block) and from restoreBydOnCluster / restoreOriginCluster. Requires PROTOCOL_VERSION bump 2 → 3 (first real bump since v1.1.6). Full specification in docs/PLAN_v1.3.x_FULL_PROXY.md §7.b (this repo, this branch). Touched files File Change app/build.gradle versionCode 199 → 200, versionName "1.2.8" → "1.2.9" app/src/main/java/com/byd/dashcast/MainActivity.java doKillApp serialize move → kill via LaunchCallback (~50 LoC); restoreBydDashboard + originCluster pre-remove captured pkg from session set (~14 LoC total) app/src/main/java/com/byd/dashcast/AdbLocalClient.java verifyForceStop active kill -9 escalation (~40 LoC) docs/PLAN_v1.3.x_FULL_PROXY.md NEW §7.b documenting the typed-verb robust fix for v1.3.x CHANGELOG.md v1.2.9 build 200 entry Net stats: 5 files changed, 454 insertions, 14 deletions. No new permissions. No new dependencies. No new strings. No protocol bump (still PROTOCOL_VERSION=2). Behavioural contract DL3 (Seal EU production): zero behaviour change outside the targeted fixes. The wm overscan -d 1 cluster routing, all typed verbs, all sendInfo Parcel layouts — all bit-for-bit identical to v1.2.8. DL5 (testeur): same — shared MainActivity / AdbLocalClient code paths. The DL5-specific IME bridge / display-0 hard guards / displayId override (from v1.2.6 — v1.2.8) are untouched. DL2: completely unaffected. No cluster path → only verifyForceStop escalation could trigger there, but DL2 doesn't ship the typed verbs that gate this code path. Validation plan (suggested in-car checks) (a) DL3 Seal EU — Bug 1 Install v1.2.9 (over v1.2.8 — should NOT prompt for uninstall). Start cluster projection → launch Waze on cluster. Long-press the Waze tile in DashCast → tap Kill. Expected: Waze process dies, cluster goes blank. Open Recents on the head unit. The Waze tile may still appear (known TaskRecord limitation — see roadmap). Tap it. Expected: Waze relaunches fresh (no prior state from display 1) — proves the move-then-kill race is gone. (b) DL3 Seal EU — Bug 2 Start cluster projection → launch Waze on cluster. Tap Stop Projection. Expected: cluster restored to BYD dashboard AND NO Waze flash on the head unit (previously: Waze popped fullscreen ~1 s after restore). Logcat tag MainActivity should show mSessionClusterPackages remove com.waze before any moveSessionAppsToMainDisplay line. (c) DL5 testeur Same two scenarios as (a) and (b), same expected results (shared code path). (d) DL2 Install, navigate UI, no cluster path triggered, zero behaviour change. (e) Logcat hygiene Search for verifyForceStop ... escalating kill -9 in logcat. Should only appear when the typed forceStopPackage left a surviving PID (rare on a healthy device) — confirms the escalation is dormant in nominal operation. Install Direct sideload (BYD file manager → tap APK): DashCast-v1.2.9-debug.apk Or via DashCast's built-in OTA updater (Settings → Check for updates) once GitHub picks up this release. Branch & tag Branch: beta/1.2.0-dilink5 Commit: e7f1a90 Tag: v1.2.9 Previous: v1.2.8 (build 199, May 22, 2026) Next planned: v1.3.0 — full ProxyDaemon migration (see docs/PLAN_v1.3.x_FULL_PROXY.md) LinksAPK download GitHub release page
-
DashCast — BYD Cluster Launcher & Mirror
DashCast v1.2.8DashCast v1.2.8 (build 199) — Pre-release Branch: beta/1.2.0-dilink5 Target: DiLink 5 testers (no impact on DiLink 3 production users) Built from: commit 7e94519 Overview Four coordinated DL5 payloads built on top of the v1.2.7 tactile fix, all gated behind Platform.isDiLink5(Context) so the DL3 production hot path is bit-for-bit identical to v1.2.7. (A) Keyboard bridge — manual ⌨ button + KeyboardBridgeActivity Problem. Field report (22/05/2026, post-v1.2.7): touch injection on the DL5 cluster mirror now works (Yandex maps reacts to taps), BUT tapping an EditText inside Yandex does NOT pop up the system IME on the head unit. Root cause. WindowManager per-display IME isolation routes the focused-window IME request to display 3 (the 1×1 shared_fission_bg_XDJAScreenProjection shadow framebuffer) where the IME has nowhere to render. No clean override exists on API 32 without MANAGE_DEVICE_POLICY_LOCAL_OVERRIDE (System API, unavailable on BYD-AUTO ROM). Workaround. A new translucent landscape KeyboardBridgeActivity is launched on display 0 (head unit) with a hidden EditText that triggers the native IME on tap. A TextWatcher computes the per-character diff and forwards each typed character to the cluster via ClusterInputForwarder.injectKeyEvent(KeyEvent), which already routes through the existing TRANSACT_INJECT_KEY daemon transaction (uid 2000, holds INJECT_EVENTS). KeyCharacterMap.load(VIRTUAL_KEYBOARD).getEvents(char[]) converts each character into a meta-state-aware KeyEvent[] (handles SHIFT for capitals, ALT for symbols). Fallback mapAsciiToKeyCode for degenerate ASCII when KCM lookup fails. Backspace is translated to N×KEYCODE_DEL based on before count from TextWatcher. EditText is reset after 80 characters to prevent unbounded growth. A new ⌨ button (btn_keyboard_bridge, 36 dp, tertiary-container colours) is inserted in the toolbar after the Split-screen button, visibility-gated to DL5 only (DL3 → GONE). (B) Auto-popup via ClusterImeWatcherService (AccessibilityService) Manual ⌨ taps work, but the UX target is automatic pop-up on EditText focus. Android does not expose an in-process focus listener for foreign apps' windows, so the only supported path is an AccessibilityService. New service com.byd.dashcast.ime.ClusterImeWatcherService listens for TYPE_VIEW_FOCUSED | TYPE_WINDOW_STATE_CHANGED events (feedbackGeneric, notificationTimeout=100, canRetrieveWindowContent=true). Five-guard filter chain — each event must pass ALL of these to trigger: Platform.isDiLink5(this) — DL3 strictly never triggers (refreshed every event). eventType == TYPE_VIEW_FOCUSED. ClusterService.sIsRunning — hard gate: only react while the DashCast cluster projection is actually live. If DashCast is closed, the service is registered with the system but does nothing. event.getDisplayId() > 0 — strictly secondary displays only, blocks display 0 and the unknown-id (-1) case on pre-API-30 fallbacks. node.editable && pkg != getPackageName() — no self-trigger on the bridge's own EditText. Additional safety: 600 ms debounce via mLastLaunchAt. KeyboardBridgeActivity.isShowing() static check (set in onStart/onStop/onDestroy) to skip launch when the bridge is already visible. Every callback wrapped in try { … } catch (Throwable t) { AppLogger.e(…); } — the service never crashes the host app. The launch sends an intent with EXTRA_AUTO_OPENED=true so the bridge can distinguish manual vs. auto invocations. (C) On-boarding banner for the AccessibilityService Android requires the user to enable an AccessibilityService manually in Settings (no programmatic enable possible by design). A new MaterialCardView card_ime_a11y_banner (tertiary-container, corner 12 dp) is inserted in MainActivity after the top bar. It shows on first launch when all three are true: Platform.isDiLink5(this) The service is not yet in Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES (parsed colon-separated, comparing both flattenToString and flattenToShortString). The user has not dismissed it (PREF_IME_BANNER_DISMISSED). Three actions: Enable → deep-links to Settings.ACTION_ACCESSIBILITY_SETTINGS. Toast Clavier automatique actif ✓ shown on return to MainActivity if the service is then enabled. Later → session-hide only, banner reappears next launch. Dismiss → persists PREF_IME_BANNER_DISMISSED=true, banner never shown again unless prefs are cleared. Fully no-op on DL3 (never shown). (D) DL5 resize sliders — dynamic cluster routing Problem. The per-app resize panel (btn_toggle_resize → SeekBars W/H → Apply) was a no-op on DL5. Two hard-coded bugs: MainActivity.btnResizeApply and autoApplyInsetsIfNeeded both issued wm overscan W,H,W,H -d 1 (cluster id on DL3 only — on DL5 the cluster lives on display 3 or 4, so the command silently failed). ClusterService.resizeActiveTask hard-coded the freeform Rect to 1920 - insetH, 720 - insetV (DL3 cluster size). Fix: Both MainActivity call sites now resolve the cluster display id dynamically via ClusterService.getDisplayId() → DashboardDisplayHelper.getKnownClusterDisplayId() — the same source already used by every other cluster-routed code path. Skip the wm overscan call entirely when clusterId <= 0 (no cluster connected — never silently retarget to display 0). ClusterService.resizeActiveTask and the inline post-moveTaskToDisplay resize block now compute bounds from mInputForwarder.getClusterWidth()/getClusterHeight() (set live from Display.getSize() in setClusterDisplay), with explicit fallback to 1920×720 if either getter returns 0. DL3 behaviour stays bit-for-bit identical (DL3 cluster IS 1920×720; fallback path matches the old hard-coded values). On DL5 the bounds now reflect actual cluster dimensions reported by the system. (E) Head-unit display-0 hard guards (defense in depth) Concern. Even with the per-call-site clusterId > 0 checks in (D), a regressed caller could still ship a wm overscan|size|density … -d 0 to the head-unit display 0 and shrink the main UI (the v1.2.5 DL2 incident playbook). Three independent layers: (E1) ShellGateway.WM_DISPLAY_ZERO — new regex ^\s*wm\s+(?:overscan|size|density)\b.*\s-d\s+0\b.*$ blocks any wm verb explicitly targeting display 0. Checked at the top of execShellWithResult before both the proxy-ON typed-verb path AND the legacy AdbLocalClient fallback — neither can reach the system. Blocked commands emit AppLogger.e("BLOCKED wm verb on display 0 (head unit): …") and the caller's Callback.onError("blocked: wm command targets display 0 (head unit)"). (E2) New abort in ClusterService.resizeActiveTask: if mDisplayHelper.getKnownClusterDisplayId() <= 0 at call time, log and return without invoking IActivityTaskManager.resizeTask. Without this, a stale taskId that happens to live on display 0 (because a prior cluster-move failed silently) would have applied DL3 freeform bounds to the head-unit task. (E3) The two MainActivity clusterId > 0 guards from (D) form the outermost UI-level layer. Coverage matrix: to reach display 0 a caller would have to simultaneously bypass the UI guard, the cluster-connected guard in resizeActiveTask, AND the regex at the shell gateway — three independent layers, no single point of failure. Behavioural contract Platform Result DL3 production (BYD Seal EU) ⌨ button GONE, banner hidden, AccessibilityService inert (!isDiLink5 short-circuit), resize sliders identical to v1.2.7 (cluster id resolves to 1, bounds 1920×720, command -d 1), display-0 guards never fire. DL5 testeur (BYD-AUTO) ⌨ button visible, bridge forwards keystrokes to cluster apps. With AccessibilityService enabled, EditText focus on cluster auto-launches bridge in ~100 ms. Resize sliders actually work. Display-0 protections active. DL2 Unaffected — no cluster surface ever runs on DL2; AccessibilityService stays inert if accidentally enabled. v1.2.5 DL2 hard-ban still in place + new WM_DISPLAY_ZERO regex adds another layer. Touched files NEW: KeyboardBridgeActivity.java, ime/ClusterImeWatcherService.java, res/xml/accessibility_ime_watcher.xml MODIFIED: AndroidManifest.xml, MainActivity.java, ClusterService.java, dashboard/ClusterInputForwarder.java, beta/ShellGateway.java, res/layout/activity_main.xml, res/values/strings.xml, app/build.gradle No new permissions. No protocol bump (PROTOCOL_VERSION=2 unchanged). All daemon traffic reuses existing TRANSACT_INJECT_KEY=3. Validation plan DL5 testeur Install, start cluster projection, open Yandex on cluster. Tap ⌨ button → bridge appears on head unit, typing forwards to Yandex search bar. Enable AccessibilityService in Settings (Accessibility → DashCast Cluster IME Watcher). Tap Yandex search bar on cluster → bridge auto-launches within ~100 ms. Open Adjust panel, slide W/H, press Apply → Yandex window on cluster recomputes bounds. Head unit unaffected (visual proof display 0 is untouched). Banner appears on first launch. Enable → return to app → expect Toast Clavier automatique actif ✓. DL3 Seal EU (regression check) Install. Confirm ⌨ button is NOT visible in toolbar, banner is NOT shown. Resize sliders behave exactly as v1.2.7. Logcat tag ClusterImeWatcherService never logs anything past !isDiLink5 → return. DL2 (regression check) Install. Everything works exactly as v1.2.7. Install Download DashCast-v1.2.8-debug.apk below and install via: adb install -r DashCast-v1.2.8-debug.apk Or via OTA: the device's existing DashCast install will see versionCode 199 > 196 and offer the update. LinksAPK download GitHub release page
-
eSim provisioning
@TangTang You need to replace the built-in esim with a physical sim slot. But now there is a problem with connecting Chinese machines outside of China. A week ago, BYD updated their servers and the Chinese cars stopped connecting to the servers if cars are located outside of China. You can connect to the Telegram channel https://t.me/byd_ota They are currently trying to solve this problem
-
European firmware on Chinese variants
@TangTang The global firmware is not compatible with the Chinese one, do not try to install it, you will get a brick.
-
DashCast — BYD Cluster Launcher & Mirror
DashCast v1.2.7DashCast v1.2.7 — Pre-release (versionCode 196) DL5 tactile fix follow-up to v1.2.6. v1.2.6 made the cluster mirror visible on DL5 (layerStack 3→2 override let us capture the composed fission output instead of the 1×1 shadow framebuffer). v1.2.7 attempts to make the cluster mirror touchable on DL5, symmetric to the layerStack work, and instruments the daemon's input injection path so the next M7 capture reports exactly where the touch chain breaks if this fix isn't sufficient. Field test evidence (22/05/2026, v1.2.6 on DL5 testeur) ✅ Preview shows actual cluster composite content (Yandex visible in TextureView in real time). ❌ Touch does NOT reach Yandex in preview OR in fullscreen mirror. Log: ClusterInputForwarder Cluster dimensions: 1920x720 displayId=3, Daemon Binder connected — touch/key injection via uid=2000, daemon holds INJECT_EVENTS, no injectMotion failed warning in logcat, no touch → debug line (filtered from the field report). Yandex's window on WMS: mDisplayId=3 rootTaskId=79 Session{… 7775:u0a10124} → daemon was correctly targeting setDisplayId(3). BUT: WMS also exposes a separate mDisplayId=2 (visible in mWindowsForAccessibilityObserver={…, 2=…, 3=…, 4=…}) backing the fission_bg_XDJAScreenProjection 1920×720 composite — that's where the user actually sees the apps. Hypothesis: on DL5 the InputDispatcher either clips events injected on displayId=3 against its degenerate 1×1 framebufferSpace, refuses to route into PRESENTATION displays with such dims, or the WMS-side Yandex window on displayId=3 isn't actually input-eligible because it's composited away by containerservice into displayId=2's surface. Changes ClusterMirrorManager.applyDl5DisplayIdOverride(Context, int) — sibling helper to v1.2.6's applyDl5LayerStackOverride. Same shape, same DL3-safe contract: Platform.get().isDiLink5(ctx) wrapped in try/catch (any failure returns input unchanged), DL3 returns input unchanged, DL5 rewrites 3 and 4 to 2 with explicit AppLogger.i DL5 override: displayId N → 2 (touch injection on composed cluster face) log line. Wired in startMirrorViaDaemon right before data.writeInt(clusterDisplayId) — the value of sClusterDisplayId stored statically inside MirrorDaemon (and used by injectMotion for MotionEvent.setDisplayId) now becomes 2 on DL5 instead of 3. MirrorDaemon.injectMotion/injectKey — first-event + failure instrumentation via out(). v1.2.6 made setupMirror trace appear in /data/local/tmp/mirrordaemon_latest.log (the file M7 reads) but the injection paths still only logged failures to logcat. New volatile boolean sMotionFirstLogged/sKeyFirstLogged flags (reset in setupMirror so each session re-arms once) make the very first successful injection emit out("injectMotion FIRST OK displayId=N setDisplayIdAvail=true|false action=K x=X y=Y ret=R") to the file log; any subsequent exception emits out("injectMotion EXCEPTION displayId=N action=K err=ClassName: message"). Pre-check path (ev==null || sInputManager==null) also emits a one-shot diagnostic. Same instrumentation pattern for injectKey. Next M7 capture on DL5 will tell us: did the override take effect (displayId=2 in the FIRST OK line), did setDisplayId reflection survive (setDisplayIdAvail=true), did InputManager accept the event (no EXCEPTION line). Behavioural contract DL3 production (BYD Seal EU) — bit-for-bit identical: applyDl5DisplayIdOverride returns input unchanged on isDiLink5=false so clusterDisplayId written to the daemon's MIRROR_START parcel stays exactly what Display.getDisplayId() returned on DL3. Daemon out() additions are pure log writes; the Log.w calls are preserved. No public API change, no protocol bump (PROTOCOL_VERSION="2"). DL5 testeur — daemon's setDisplayId target shifts from 3 to 2 for touch injection. Mirror visual stays exactly as v1.2.6 (layerStack capture from 2 unchanged). M7 detail now contains the actual displayId/setDisplayIdAvail/ret triplet of the first injected event so we can triage further if Yandex still doesn't respond. DL2 testeur — completely unaffected (no cluster code path triggered). Validation plan DL5 in-car — install, activate cluster, launch Yandex/Maps on cluster, then tap on the in-app preview surface (or enter fullscreen via 📺 and tap there): expected — Yandex now responds to touch (map pans, buttons react). Logcat tag ClusterMirrorManager should contain DL5 override: displayId 3 → 2 exactly once per mirror session, and startMirrorViaDaemon ✓ layerStack=2 1920×720 displayId=2 (note displayId=2 instead of =3 like v1.2.6). If still not working — open Diag → Mirror → Lancer tous, M7 detail will show the first injectMotion FIRST OK displayId=2 setDisplayIdAvail=true action=0 x=… y=… ret=… line: ret=false → InputManager refused (likely needs displayId=0 + global routing, or WMS input monitor). setDisplayIdAvail=false → reflection failed on this ROM, the event went to displayId=0 by default → DashCast self-consumed it (explains v1.2.6 silent failure). No injectMotion FIRST OK line at all → the OnTouchListener never fired (layout/event consumption issue), pivot to View-tree debug. DL3 Seal EU — install, activate cluster, run any cluster app, confirm fullscreen mirror tactile still works exactly as before. Logcat should NOT contain any DL5 override: displayId line. DL2 — install, navigate UI, no cluster path triggered, zero behaviour change. Touched files app/build.gradle (versionCode 195 → 196, versionName 1.2.6 → 1.2.7) dashboard/ClusterMirrorManager.java (+22 LoC, +applyDl5DisplayIdOverride helper, +1 call site) daemon/MirrorDaemon.java (+30 LoC, +2 volatile flags, reset in setupMirror, instrumented injectMotion/injectKey) CHANGELOG.md No new permissions, no new dependencies, no new layouts, no new strings. Branch: beta/1.2.0-dilink5 (not merged to main) APK: DashCast-v1.2.7-debug.apk (14 MB) Detected as update from: v1.2.6 (versionCode 195) and earlier. LinksAPK download GitHub release page
-
DashCast — BYD Cluster Launcher & Mirror
DashCast v1.2.6DashCast v1.2.6 — Pre-release (versionCode 195) Two coordinated payloads bundled into a single OTA: a zero-behaviour proxy daemon performance pass (P1–P4) and a focused DL5 mirror investigation gated on Platform.isDiLink5 (DL3 strictly untouched). (A) Beta Engine proxy daemon — 4 perf passes (P1–P4) Lockless hot paths in the typed-verb dispatcher. Zero protocol bump (PROTOCOL_VERSION = "2"), zero public API change, zero behaviour change on DL3 reference (BYD Seal EU) or DL5 testeur. P1 — Lock-free hot-path verbs in BetaProxyClient. sBinder promoted to volatile; synchronized (LOCK) removed from setOverscan, getPidsByPackage, autoContainerSendInfo, forceStopPackage, runShell, runPhase4Probes, ping. sDaemonUid / sDaemonPid / sDaemonVer now volatile so the public getters are lockless too. connect(), the receiver and handshake() still take LOCK for publication — the only place strictly needed. Resize SeekBar (~30 setOverscan/s while dragging) and reconcile loop (getPidsByPackage every ~5 s) now dispatch in parallel through the daemon's binder pool instead of queuing behind any in-flight runShell. P2 — DeathRecipient hooked on the live binder. isConnected() now uses the cheap local isBinderAlive() flag instead of issuing a full pingBinder() IPC roundtrip on every typed verb. Per-verb latency roughly halves (~10 ms → ~5 ms). Receiver unhooks the previous death recipient before swapping a fresh binder; linkToDeath is best-effort caught for the vanishing race between stale-broadcast check and link. P3 — ThreadLocal<byte[]> scratch in Phase4Verbs.getPidsByPackage. Was allocating a fresh byte[256] per /proc/<pid>/cmdline scan iteration (~241 allocations per reconcile tick). The daemon's binder thread pool reuses threads → one buffer per thread for the daemon's lifetime, zero allocation, zero GC pressure. Loop body and matching semantics byte-for-byte unchanged. P4 — ByteArrayOutputStream in ProxyDaemonMain.runShell. Replaces the per-line BufferedReader.readLine() + StringBuilder pattern. Reads the full child stream in 4 KiB chunks into a single ByteArrayOutputStream, decodes once with toString("UTF-8"), strips trailing \r/\n to exactly reproduce the legacy line-joiner semantics. ~1000 transient allocations saved on a 300-line dumpsys SurfaceFlinger. Wire protocol: unchanged. An app-update that lands while a daemon from build 192–194 is still alive transparently talks to the old daemon until the next bootstrap kills it. No incompatibility possible. (B) DL5 mirror investigation — 3 surgical fixes First DL5 in-car test of the build 192–194 mirror path confirmed end-to-end routing works (Yandex on cluster mDisplayId=3 rootTaskId=50, daemon-side startMirrorViaDaemon ✓ layerStack=3 1920×720 displayId=3) but the in-app preview surface stays black. Root cause traced to the DL5 fission architecture: apps render on shared_fission_bg_XDJAScreenProjection_0/1 (layerStack=3/4, framebufferSpace bounds 1×1 — degenerate shadow displays), then com.byd.containerservice re-composes the selected slot into fission_bg_XDJAScreenProjection (layerStack=2, framebufferSpace bounds 1920×720 — the actual surface piped to the physical cluster panel). Mirroring layerStack=3 captures a 1×1 black buffer. B1 — DL5 layerStack override 3→2 (and 4→2) in ClusterMirrorManager. New private helper applyDl5LayerStackOverride(Context ctx, int detected) wraps Platform.get().isDiLink5(ctx) in a try/catch (any reflection failure returns input unchanged → DL3 path bit-for-bit identical). On confirmed DL5, layerStack 3 or 4 is rewritten to 2 with an explicit log line; any other value is passed through untouched. Applied inside both startMirror (app-uid direct SurfaceControl path) and startMirrorViaDaemon (daemon-uid path). Both signatures gained a leading Context parameter; sole caller MainActivity.attemptStartMirror updated. B2 — Daemon-side mirror setup logging (MirrorDaemon.setupMirror). All pre-195 mirror traces in the daemon went through Log.i (logcat-only). The on-device M7 diagnostic test reads /data/local/tmp/mirrordaemon_latest.log which only captures out() lines, so the file showed only the startup banner. Build 195 mirrors the critical-path traces to out(): setupMirror BEGIN, setupMirror createDisplay OK token=… (or FAIL createDisplay returned null), setupMirror SF dump (layerStack=N): … (or empty — token NOT in SurfaceFlinger!), setupMirror DONE ok=true, and setupMirror EXCEPTION: ClassName: message on any failure. Next M7 capture will show the full daemon chain. B3 — M10 LIVE probe fixes (MirrorTestRunner.runM10). Build 194 M10 had two known-bad hard-codings: STEP 2 / STEP 7 issued service call AutoContainer 2 … (PascalCase) but the DL5 cluster service is registered as auto_container (snake_case, confirmed by production ClusterManager), and STEP 4/5 launched com.android.settings/.Settings which BYD doesn't ship under that exact AOSP name. Build 195 replaces AutoContainer → auto_container (3×) and com.android.settings/.Settings → com.android.launcher3/.Launcher (2×). Test stays ALWAYS PASS (manual analysis of STEP 3 framebufferSpace diff and STEP 6 routing), but next run produces real sendInfo Parcel(...) output and real am start Status/TotalTime instead of two-bugs-cancelling-out. Behavioural contract DL3 production (BYD Seal EU) — bit-for-bit identical sequence of binder transactions and shell outputs as build 194. The DL5 layerStack override short-circuits via Platform.isDiLink5=false. The daemon out() additions are pure log writes. M10's diag-only changes don't touch any runtime path. DL5 testeur (BYD-AUTO) — proxy daemon perf wins apply identically. Mirror should now capture the composed 1920×720 fission output instead of the 1×1 shadow. If still black, M7 will provide the full daemon setup chain in the file log (createDisplay token state, SF dump result, projection params) — enough evidence to triage further. DL2 testeur — completely unaffected (Beta Engine disabled by default, no cluster display so mirror code path never runs). Validation plan DL5 in-car — install, activate cluster, launch Yandex/Maps on cluster, observe in-app preview during active mirror (don't open Diag first). Expected: preview now shows the actual cluster content. If still black: Diag → Mirror → Lancer tous → M7 contains the full daemon setup chain → paste in issue. DL5 in-car — re-run M10: STEP 2 should show successful Parcel(00000000 00000001) from auto_container, STEP 4/5 should show Starting: Intent { ... cmp=com.android.launcher3/.Launcher } with a real Status/TotalTime, STEP 6 still shows rootTaskId=N mSession=... now correlated with launcher3. DL3 Seal EU — install, activate cluster, launch Yandex/Waze: zero behaviour change vs build 194. Logcat tag ClusterMirrorManager should NOT contain any DL5 override line. Resize SeekBar smoothness (any platform) — open Settings → Marges, drag the slider 0 → 80 → 0: expect noticeably smoother behaviour vs build 194 (P1+P2 → ~2× faster per-tick, ~3–5 ms instead of 8–12 ms). Touched files app/build.gradle (versionCode 194 → 195, versionName 1.2.5 → 1.2.6) beta/BetaProxyClient.java (~45 LoC, no public API change) beta/proxy/Phase4Verbs.java (+6 LoC) beta/proxy/ProxyDaemonMain.java (~20 LoC) dashboard/ClusterMirrorManager.java (~30 LoC, +Context param + DL5 helper) daemon/MirrorDaemon.java (~12 LoC, out() mirrors) mirror/MirrorTestRunner.java (~10 LoC, M10 fix) MainActivity.java (+2 args in attemptStartMirror mirror startup) CHANGELOG.md No new permissions, no new dependencies, no new layouts, no new strings. Branch: beta/1.2.0-dilink5 (not merged to main) APK: DashCast-v1.2.6-debug.apk (14 MB) Detected as update from: v1.2.5 (versionCode 194) and earlier. LinksAPK download GitHub release page
-
DashCast — BYD Cluster Launcher & Mirror
DashCast v1.2.5DashCast v1.2.5 — build 194 Branch: beta/1.2.0-dilink5 · Pre-release · Not merged to main Summary New M10 — Cluster routing LIVE diagnostic test in the Diag → Mirror tab. End-to-end probe that fires sendInfo(1000, 16) on the BYD AutoContainer service, snapshots the SurfaceFlinger framebufferSpace of every XDJA-fission display before and after the call, then attempts to route com.android.settings/.Settings via am start-activity --display 3 and --display 4, dumps the resulting WindowManager root-task topology, and cleans up. All seven step outputs are concatenated into the test detail and shipped via Telegram with the existing Envoyer (.log) button. What problem does M10 solve? Build 192 (D28) visually confirmed that sendInfo(1000, 16) opens a black projection window on the cluster. Build 193 added the read-only Mirror diag suite (M1-M9) that gives full SurfaceFlinger and WindowManager snapshots. But no single field run has captured the BEFORE/AFTER framebufferSpace transition AND the activity-routing outcome together — so we still cannot say with certainty: Which of the inactive 1×1 fission slots (display 3 or 4) becomes the active 1920×720 cluster framebuffer after sendInfo(16). Whether an activity launched with am start --display N actually lands on that display (and not silently re-routed to display 0 by the XDJA fission compositor like the DL5 ROM is known to do in some cases). M10 closes that gap in one shot. Test steps (all captured in one detail blob) Step What it does STEP 1 dumpsys SurfaceFlinger | grep -E 'DisplayDevice\{|framebufferSpace=ProjectionSpace|layerStack=[0-9]' — BEFORE state of fission/XDJA displays STEP 2 service call AutoContainer 2 i32 1000 i32 16 s16 "" — opens cluster projection STEP 3 Same grep as STEP 1, 2 s after STEP 2 — AFTER state. The display whose framebufferSpace flips from Rect(0, 0, 1, 1) to Rect(0, 0, 1920, 720) is the cluster target. STEP 4 am start-activity -W --display 3 -n com.android.settings/.Settings — captures synchronous -W result (Status / TotalTime) STEP 5 Same on --display 4 STEP 6 dumpsys window | grep -E 'Display: mDisplayId=|mCurrentFocus=|mFocusedApp=|rootTaskId=' — which display actually hosts the Settings root task STEP 7 Cleanup: am force-stop com.android.settings + sendInfo 18 (close projection) + sendInfo 0 (restore Qt native video stream) Why the ADB-local shell (not direct Binder) DL5 systematically returns SIGNATURE_NO_MATCH when the app uid calls ServiceManager.getService("autocontainer") — the BYD AutoContainer service is signature-protected against third-party packages. The ADB-local relay (uid 2000 / shell) is the only Binder path that works from a non-system APK, so M10 routes the service call through AdbLocalClient.executeShellWithResult exactly like the rest of the suite. Status semantics M10 always reports PASS — the analysis is intentionally manual. A hard PASS/FAIL would have required hard-coding an expected display id, but the whole point of M10 is to discover that id. The result message reads: live routing run complete — compare STEP 1 vs STEP 3, then STEP 6 The operator analyzes the raw text in the shipped report. Side-effect contract Two non-trivial side-effects, both pre-validated as user-safe: sendInfo 16 opens a black projection window on the cluster for ~6 seconds (visually confirmed harmless in build 192 D28). Settings briefly launches on display 3 or 4 (or fails to, depending on the ROM) and is force-stopped. STEP 7 always runs even if STEPS 4 / 5 fail, so the cluster is always returned to its baseline state regardless of intermediate failures. Files changed File Change app/build.gradle versionCode 193 → 194, versionName 1.2.4 → 1.2.5 mirror/MirrorTestRunner.java +1 catalog entry, +1 dispatcher case, +runM10 (~85 LoC), +1 reused fbGrep constant CHANGELOG.md New 1.2.5-build194 entry at the top of the pre-releases table No new permissions, no new dependencies, no new layouts, no new strings, no new drawables. M10 reuses the existing runShellSync helper, the AdbLocalClient.executeShellWithResult infrastructure, and the existing include_diag_mirror.xml row layout (Mirror rows are generated dynamically from MirrorTestRunner.catalog() in DiagActivity.prepareMirrorTestRows, so no XML edit was needed). DL3 / DL2 behaviour The Mirror tab and M10 still run, but STEP 2 will likely return Service AutoContainer does not exist on DL3 (no XDJA fission compositor) or hit the DL2 single-display ROM (no cluster at all). The test still PASSES with the empty-output evidence, which is itself useful — it proves AutoContainer is DL5-only and confirms the auto-detection scope. Validation plan Install the OTA on a DL5 device. Open DashCast → Diag → swipe to the Mirror tab → tap Lancer tous. Wait ~25 s for M10 (longest test in the suite due to the 2 s + 1 s + 1 s settle waits). Tap Envoyer (.log) → Telegram. In the report look for [PASS] M10 then under --- detail --- read STEP 1 vs STEP 3: exactly one of fission_bg_XDJAScreenProjection_0/1/2 should have flipped from framebufferSpace=ProjectionSpace(start=Rect(0, 0, 1, 1) to =Rect(0, 0, 1920, 720). That display's layerStack= value is the cluster compositor's target layerStack. Then STEP 6 should show rootTaskId=... for a mDisplayId=3 or mDisplayId=4 entry hosting com.android.settings/.Settings — confirming the activity-routing side of the pipeline. Asset DashCast-v1.2.5-debug.apk — 14 MB, signed debug, ready for OTA install via the in-app updater. LinksAPK download GitHub release page
-
DashCast — BYD Cluster Launcher & Mirror
DashCast v1.2.4DashCast 1.2.4 (build 193) — Diag: dedicated Mirror tab + Telegram log export Pre-release • branch beta/1.2.0-dilink5 • versionCode 193 / versionName 1.2.4 TL;DR A brand-new Mirror tab has been added to the Diag screen, dedicated to investigating the DiLink 5 cluster mirror black-screen issue reported in build 192. It runs a 9-test read-only suite that captures the ground-truth SurfaceFlinger topology and the MirrorDaemon log directly on-device, saves the heavy artifacts under Download/dl5_mirror_diag/ (browsable from the BYD file manager), and ships everything via a one-tap Envoyer (.log) button that opens the standard Android share chooser — pick Telegram and the report is sent. Zero changes to the mirror code path. MirrorDaemon, ClusterMirrorManager, ClusterService and every touch/launch code path are bit-for-bit identical to build 192. DL3 mirror behaves exactly as before. DL5 mirror behaves exactly as before. This build is purely a diagnostic surface. Why this build exists Field log on a DL5 device (BYD-AUTO Android 12, cluster display shared_fission_bg_XDJAScreenProjection_0 1920×720 @ 320dpi) showed the cluster-mirror path completing successfully on the daemon side: startMirrorViaDaemon ✓ layerStack=3 1920×720 displayId=3 …but the cluster screen stayed black. Hypothesis: apps physically launched on display #3 are rendered by the XDJA fission compositor, not by SurfaceFlinger. SurfaceControl.setDisplayLayerStack(token, 3) therefore mirrors an empty SF layerStack. To confirm this without touching the production mirror code, we need the real SF topology and the daemon's own post-setup SF dump — captured by this build. What's new 1. New Mirror tab in Diag (position 8) DiagActivity grows from 9 to 10 tabs. TAB_MIRROR = 8 is inserted between DiLink 2 and Sniffer. New view field panelMirror, new methods bindMirrorPanel / prepareMirrorTestRows / bindMirrorRow / runMirrorAllTests / updateMirrorCounters / sendMirrorLog — follow the exact pattern of the existing DL5 panel for visual consistency (same status pill, same counters block, same per-test row layout via item_beta_test.xml). New layout include_diag_mirror.xml — clone of include_diag_dilink5.xml minus the per-app Spinner (no per-app target for the Mirror suite). The action row exposes two buttons: Lancer tous (run all tests) and Envoyer (.log) (share the bundled report). The Envoyer (.log) button calls AppLogger.shareWithReport(this, mirrorReport) which already routes through Intent.ACTION_SEND with a FileProvider URI → Android share chooser → the user picks Telegram from the chooser and the message goes through with the full report attached. 2. MirrorTestRunner — 9 tests (M1–M9) Self-contained suite under com.byd.dashcast.mirror. Same data model triplet (TestDef / TestResult / Status / Listener) and same single-thread executor pattern (mirror-test-runner) as the DL5 runner — intentionally not sharing nested types so a regression in either suite cannot affect the other. The suite writes all heavy artifacts (full SF dumps, daemon log copy) to /storage/emulated/0/Download/dl5_mirror_diag/, directly browsable from the BYD file manager. ID Test What it captures M1 Cluster display fingerprint (no shell) DisplayManager.getDisplays() + reflective Display.getLayerStack / getOwnerPackageName / getOwnerUid for every display: id, name, real size, density, flag breakdown, owner pkg/uid, layerStack. The WindowManager-side identity of the cluster surface, decoupled from SurfaceFlinger. M2 SurfaceControl.getPhysicalDisplayIds() (reflection) Reflective call to the hidden API plus getPhysicalDisplayToken(id) for each returned id. SecurityException expected from the app uid — the failure mode itself is useful evidence. M3 dumpsys SurfaceFlinger --list Compositor's view of every display/layer name (top 100 lines). M4 Full dumpsys SurfaceFlinger (head -300) Saved verbatim to Download/dl5_mirror_diag/dumpsys_sf_full.txt for offline inspection. M5 Filtered SF topology grep -E 'Display |layerStack|format|orientation|XDJA|fission|cluster|byd|projection|mirror' — extracts only the lines we need to map layerStack ↔ cluster display ↔ XDJA fission compositor. M6 dumpsys window cluster state grep -E 'Display #|mDisplayId|mCurrentFocus|mFocusedApp|cluster|fission|XDJA|imeLayerStack' — WindowManager's view of which apps live on which display, focus owner, IME layer. M7 MirrorDaemon log tail (200 lines) Reads /data/local/tmp/mirrordaemon_latest.log if present. This is the gold evidence: the daemon writes its own post-setup dumpsys SurfaceFlinger | grep byd_myapp_mirror here. WARN if the file is missing (daemon never launched in this session). M8 Copy MirrorDaemon log → Download/ Copies /data/local/tmp/mirrordaemon_latest.log to Download/dl5_mirror_diag/mirrordaemon_latest.log so it ships alongside the textual report. M9 Mirror token presence post-setup dumpsys SurfaceFlinger | grep -E 'byd_myapp_mirror|mybyd_preview_mirror'. If the token is absent the daemon's createDisplay silently failed; if present but cluster still black, the layerStack mapping is wrong (cluster pixels live in another compositor). 3. Behavioural contract Mirror tab is read-only diagnostic only. No shell command issued by the suite mutates display state, no wm overscan/size/density calls, no am start --display, no projection toggle. Zero changes to MirrorDaemon, ClusterMirrorManager, ClusterService or any cluster touch/launch path. DL3 mirror unchanged. DL5 mirror unchanged. Existing 9 Diag tabs unchanged. DL5 test D30 keeps its broader SF dump; the M-suite is a more curated mirror-focused subset. The DL2 hard-ban on display-resize shell commands (build 191) still owns that contract — none of the M-tests can bypass it. 4. Files touched DiagActivity.java — TAB_MIRROR = 8, TAB_SNIFFER pushed to 9, TAB_COUNT = 10, panelMirror field, bindMirrorPanel / prepareMirrorTestRows / bindMirrorRow / runMirrorAllTests / updateMirrorCounters / sendMirrorLog, showPanelForTab Mirror branch (~150 LoC added). New mirror/MirrorTestRunner.java — ~330 LoC, 9 tests + buildReport + runShellSync helper. New include_diag_mirror.xml — ~140 LoC, clone of DL5 panel minus the Spinner, action row with Lancer tous + Envoyer (.log). activity_diag.xml — +1 TabItem (@string/diag_tab_mirror), +1 panel <include>. values/strings.xml — +8 diag_mirror_* strings (FR default). build.gradle — versionCode 192 → 193, versionName 1.2.3 → 1.2.4. No new permissions, no new dependencies, no new drawables. How to validate On the affected DL5 device Install the OTA (versionCode 193 > 192, the car detects it as an update). Launch DashCast → tap Start projection so the cluster mirror gets attempted (this is what produces mirrordaemon_latest.log). Open Diag → swipe to the new Mirror tab → tap Lancer tous. Expected results: M1 PASS — at least 1 PRESENTATION display detected, layerStack/owner reported. M2 WARN or PASS — reflection may be blocked by SELinux on the app uid; the failure mode is evidence either way. M3–M6 PASS — local ADB shell present. M7 PASS — daemon log produced during the projection attempt in step 2. M8 PASS — copy to Download/dl5_mirror_diag/mirrordaemon_latest.log succeeded. M9 PASS if the mirror token exists in SF (the silent-success case we want to confirm); WARN if it does not (= the daemon's createDisplay is the failure point). Tap Envoyer (.log) → Android share chooser opens → pick Telegram → message goes through with the full report attached. The operator now has on-device, browsable via the BYD file manager: Download/dl5_mirror_diag/dumpsys_sf_full.txt — live SF topology Download/dl5_mirror_diag/mirrordaemon_latest.log — the daemon's own SF dump Plus the textual M1–M9 report attached to the Telegram message. On DL3 (BYD Seal EU, production reference) Install, open Diag, confirm default tab is unchanged (Beta Engine). Swipe to Mirror tab (visible at position 8). Tap Lancer tous — the same suite runs (M1 yields the DL3 cluster display fingerprint, the rest succeeds on shell). No regression on DL3 mirror behaviour. On DL2 (alps / k65v1 / API 28-29) Install, open Diag. Default tab is still DiLink 2 (per build 192). Mirror tab is reachable but irrelevant on DL2; the suite runs read-only without issuing any resize commands (the build-191 DL2 guard still owns that contract). Compatibility versionCode 193 (was 192) — OTA-eligible for every device currently on build 192 or earlier. versionName 1.2.4 (was 1.2.3). minSdkVersion 28 unchanged. targetSdkVersion 29 unchanged. compileSdkVersion 33 unchanged. No new runtime permission requested. Known limitations M2 is reflective access to a hidden API (SurfaceControl.getPhysicalDisplayIds). It is expected to be blocked by SELinux on the app uid on most stock ROMs — the WARN/error message is the useful signal. M7 / M8 require that the mirror was launched at least once in the current boot session, so /data/local/tmp/mirrordaemon_latest.log exists. If absent, the two tests cleanly WARN with "daemon log not found — launch mirror once then re-run". This build does not yet attempt a fix for the DL5 black-screen — it only collects evidence. The fix will land in a follow-up build once the M-suite output confirms the working hypothesis (layers live in the XDJA fission compositor, not in SF native layers). Full per-build history: see CHANGELOG.md. LinksAPK download GitHub release page
-
DashCast — BYD Cluster Launcher & Mirror
DashCast v1.2.3DashCast v1.2.3 — DiLink 2 diagnostics & DiLink 2 hardening Pre-release — versionCode 192. Targets DiLink 2 (k65, Android 9, API 28/29), DiLink 3 (Seal EU, API 29) and DiLink 5 (BYD-AUTO, API 32). All three platforms share the same APK; auto-detection routes each car to the right code path. Headlines Diag opens on the right tab automatically. DiLink 2 users now land directly on the DiLink 2 tab. DiLink 5 users still land on DiLink 5. All other devices land on Beta Engine. 40-test DiLink 2 diagnostic suite (was 30). Ten new shell-driven RE tests, including a dedicated APK extractor. One-tap APK extraction to /storage/emulated/0/Download/dl2_apks/. com.byd.cluster is always pulled first; the whole BYD/alps/xdja whitelist follows. APKs are then directly accessible from the BYD file manager and can be attached to bug reports. DL2 cluster-resize: total ban. Every shell command that could resize a physical display on DiLink 2 is rejected at three independent layers — AdbLocalClient, ShellGateway, SettingsActivity. No path can call wm size, wm density, wm overscan, am display-size, or settings put * display_* on a DL2 car. DiLink 3 and DiLink 5 are unaffected. Zero impact on DiLink 3 and DiLink 5 — same APK, same versionCode, only DL2-specific code paths changed. DiLink 2 — full 40-test catalog Tier L — reflection only (no ADB required, 15 tests) ID Test L1 Platform fingerprint (Build.* + 26 ro.* props) L2 ADB-local TCP port probe (5037 / 5554 / 5555 / 5556 / 4444) L3 Multi-display reflective scan (DisplayManager + DisplayManagerGlobal) L4 SurfaceFlinger physical displays via IBinder L5 DRM / framebuffer inventory (/sys/class/drm, /dev/graphics) L6 ServiceManager full inventory + BYD/cluster filter L7 Cluster service brute probe (33 candidate names) L8 IActivityManager method enumeration L9 ActivityOptions.setLaunchDisplayId smoke test L10 BYD packages dynamic scan via PackageManager L11 com.byd.cluster manifest deep dive L12 com.byd.appstartmanagement manifest deep dive L13 BYD SDK classpath probe (8 BYDAuto*Device classes) L14 /proc cluster process scan L15 Hidden-API reachability sanity (6 system binders) Tier S — ADB-TCP shell-driven (15 tests) ID Test S1 ADB shell reachable (id -u, id -un, pwd, uname -a) S2 Runtime properties (getprop, filtered) S3 Display state (wm size, wm density, wm overscan) S4 dumpsys display full inventory S5 dumpsys SurfaceFlinger physical displays S6 service list filtered BYD/cluster S7 AutoContainer probe (service call AutoContainer 1 + snake-case variants) S8 pm list packages -f BYD/alps/xdja S9 Activity stack snapshot (dumpsys activity) S10 SELinux + sepolicy state (getenforce, id -Z) S11 Daemon process scan (ps -A, filtered) S12 System settings BYD filter (system / secure / global) S13 Display launch probe (`am start --display 0 S14 Hardware fingerprint (cpuinfo + meminfo + df) S15 Filesystem inventory (BYD / DRM / framebuffer) Tier S — new in build 192 (RE-oriented, 10 tests) ID Test S16 BYD packages discovery via shell (pm list packages -f) — populates the discovery cache used by S17 S17 APK extraction → /storage/emulated/0/Download/dl2_apks/. com.byd.cluster is extracted first, then appstartmanagement, containerservice, smarttravel, commander, freedom, overdrive, windowmanagement, crosscontrol, clusterdebug, car.server, plus any package whose name contains cluster, fission, projection, dilink, magicwindow, mirror. Re-extraction is cache-aware (size check via stat -c %s). Filename pattern: <pkg>_v<versionCode>.apk S18 dumpsys window filtered (focused / displays / overscan / IME / stacks) S19 dumpsys SurfaceFlinger --list + HWComposer topology grep S20 Recent logcat tail (200 lines, BYD/cluster filter) S21 dumpsys package com.byd.cluster full (250 lines — manifest, services, providers, permissions) S22 dumpsys package com.byd.appstartmanagement full S23 BYD framework JARs + permission XMLs (/system/framework, /vendor/framework, /system/etc/permissions, /vendor/etc/permissions) S24 am stack list + am task list (API 28 only — direct evidence of multi-display task placement) S25 Crash logs (tombstones + ANR + dropbox, BYD-filtered) DL2 cluster-resize hard-ban (build 191) DiLink 2 cars must never receive a display-resize command — empirical evidence shows the cluster panel reacts unpredictably and can require a reboot. Build 191 enforces total prohibition at three independent layers: AdbLocalClient — every outgoing wm size, wm density, wm overscan, am display-size, settings put system display_*, settings put secure display_*, settings put global display_* is intercepted and dropped before being written to the local-ADB socket when the platform reports DL2. ShellGateway — same predicate enforced in the higher-level shell façade used by Beta Engine / overscan / mirror / projection paths. SettingsActivity — the overscan and resize sliders are disabled and labelled DiLink 2 — resize banned on detected DL2 devices. DiLink 3 (Seal EU) and DiLink 5 (BYD-AUTO) are unchanged: resize commands continue to be forwarded normally on those platforms. Platform detection DiLink 2 is detected when all of the following hold (cumulative AND, cached at process start): Build.BRAND.equalsIgnoreCase("alps") && Build.PRODUCT.toLowerCase().contains("k65") && (Build.VERSION.SDK_INT == 28 || Build.VERSION.SDK_INT == 29) DiLink 3 (Seal EU): Build.MANUFACTURER == BYD && Build.PRODUCT == byd_eu_* && API 29. DiLink 5 (BYD-AUTO): Build.MANUFACTURER == BYD-AUTO && API 32. The three predicates are mutually exclusive. Each platform only sees its own code path. Operator workflow on DiLink 2 Open DashCast → Diag (lands directly on DiLink 2 tab). Tap Lancer tous — 40 rows complete sequentially. S17 reports N APK(s) in Download/dl2_apks/. Open the BYD file manager → Download → dl2_apks → com.byd.cluster_v<X>.apk (and the rest of the BYD apk set) is ready to pull off the car. Tap Envoyer (.log) in DashCast → full diagnostic log goes out via Telegram. Compatibility DiLink 2 (alps / k65 / API 28–29): full feature set, 40 tests, resize banned, Diag opens on DL2 tab. DiLink 3 (BYD Seal EU / API 29): unchanged; Diag opens on Beta Engine. DiLink 5 (BYD-AUTO / API 32): unchanged; Diag opens on DiLink 5 tab. Install In-app updater: tap Vérifier les mises à jour in Settings (with Include pre-releases enabled). The car will detect this build as an update if its installed versionName is lower than 1.2.3 or if its versionCode is lower than 192. Manual install: download DashCast-v1.2.3-debug.apk below and pm install -r it via ADB. LinksAPK download GitHub release page
-
BYD Great Han spotted: a new flagship sibling to the Great Tang
Recent spy shots of the BYD Great Han undergoing road tests have surfaced, revealing the executive-level sedan in light camouflage. The latest images provide a clear look at the vehicle’s elongated silhouette, suggesting that it is already in its final testing phases. While BYD has yet to release official details regarding the Great Han’s debut, CarNewsChina expects a strategic rollout for the fourth quarter of this year. Following the rescheduled launch of the BYD Great Tang on June 8, the Great Han is expected to appear in Ministry of Industry and Information Technology (MIIT) filings by June or July. Pre-sales are predicted to commence in August, with a formal market launch in September to capitalise on the “Golden October” peak sales season. The Great Han is positioned as a technological tour de force for the brand. It is expected to feature a range of up to 1,000 km, supported by BYD’s second-generation Blade Battery and flash-charging technology. Renderings of the Great Han. In terms of design, the Great Han adopts the latest family aesthetic also seen on the Great Tang. Renderings and spy photos suggest a prominent through-type LED light strip and vertical ventilation inlets integrated with daytime running lights. The executive sedan is also expected to offer premium options such as two-tone paint schemes and large, colour-matched brake callipers. The interior of Great Han. Inside, the cabin shares a high degree of DNA with the Great Tang, featuring a similar premium layout and steering wheel design. This “dual-flagship” approach aims to solidify BYD’s position in the high-end market. For reference, the Great Tang EV was previously announced with a pre-sale price range of 250,000 yuan (36,800 USD) to 320,000 yuan (47,100 USD). Given its positioning and enhanced range – surpassing the Great Tang EV’s 950km – the Big Han is expected to be a formidable competitor in the premium EV segment The rear of Great Han.
-
BYD May 28 intelligent driving strategy event sparks speculation over new God’s Eye rollout
BYD announced it will hold an intelligent driving strategy conference on May 28, marking the company’s second major technology event of 2026 after its March 5 launch of Blade Battery 2.0 and flash charging, according to IT-home. The automaker did not disclose details of the upcoming event. However, Chinese automotive commentators and investor discussions over the past two days have increasingly linked the conference to intelligent driving, AI training systems, and wider deployment of advanced driver-assistance functions across mainstream vehicles. Earlier this month, BYD executive vice president and Americas president Stella Li said in a media interview that the automaker could soon unveil another major technology following its recent battery and charging announcements. Li, who joined BYD in 1996, has played a central role in the company’s global expansion strategy. Speculation centres on the intelligent driving expansion Recent Chinese discussions have focused on several possible directions for the conference, including: wider rollout of God’s Eye intelligent-driving systems expansion of city navigation assistance functions lower-cost deployment of advanced ADAS hardware updated AI training systems using virtual scenarios additional software and computing announcements tied to autonomous driving BYD’s intelligent-driving systems were already generating more than 150 million km of effective driving data daily, while cumulative deliveries of God’s Eye-equipped vehicles exceeded 2.5 million units by the end of 2025. Several Chinese automotive commentators additionally connected the event to BYD’s previously disclosed “AI agent + world model” development framework, which uses virtual training scenarios to improve rare-event handling in intelligent-driving systems. Flash charging remains central to BYD’s technology push The May 28 conference follows BYD’s March technology launch, during which the company introduced the Blade Battery 2.0 and its flash-charging platform. BYD said the new system could charge from 10% to 70% in five minutes and from 10% to 97% in nine minutes, while also announcing plans to build 20,000 flash-charging stations during 2026. A recent thermal analysis of BYD’s flash-charging system examined temperature differences between charging hotspots and the battery management system during high-rate charging, with reported gaps reaching 6.5 °C under testing conditions. Another teardown published this month showed a 170-cell Blade Battery pack after a 40-hour freeze test, with the engineering team defending an eight-hour dismantling process used during analysis. Updated Atto 3 launched ahead of the conference The strategy conference also comes one day after BYD launched the updated BYD Atto 3 in China, where the model is sold as the Yuan Plus. The refreshed version added flash charging and up to 120 km of additional range, with pricing starting at about USD 16,600. Sales context Domestic sales of the Yuan Plus reached 5,111 units in April 2026, down 21.9% month-on-month and 58.4% year-on-year. March sales totalled 6,540 units, while January-February combined sales reached 4,135 units. The model accounted for 3.4% of BYD brand sales in April 2026 based on China EV DataTracker figures provided for this report. BYD Atto 3 sales in China. Credit: China EV DataTracker
-
DashCast — BYD Cluster Launcher & Mirror
DashCast v1.2.2DashCast 1.2.2 (build 188) — DL5 app launch via ADB shell + wm overscan gated on DL3 Pre-release on branch beta/1.2.0-dilink5. Not merged into main. versionCode 187 → 188 — Android's package manager will detect this as a regular update of any installed 1.2.1 build. This build is the direct field-driven follow-up to 1.2.1 (build 187). The DL5 field log byd_report_20260522_132030.log showed the new projection wiring from 1.2.1 working perfectly up to the display-ready event, then crashing at the next step (app launch) with a SecurityException. Build 188 fixes that and also addresses the secondary Unknown command: overscan noise visible in the same log. What the build 187 log told us The DL5 testeur device confirmed the projection trigger and display resolution work as designed: [ClusterManager] DL5 activation path: sendInfo(16) only on auto_container [AdbLocalClient] sendInfo ADB: service call auto_container 2 i32 1000 i32 16 s16 "" 2>&1 [AdbLocalClient] sendInfo ADB(1000,16) → Result: Parcel(00000000 00000001 '........') [ClusterManager] DL5 cluster display ready: id=3 name=shared_fission_bg_XDJAScreenProjection_0 [ClusterService] Cluster display connected: id=3 [ClusterInputForwarder] Cluster dimensions: 1920x720 displayId=3 But the very next phase (launching the user's selected app on display 3) failed: [ClusterService] Launching via IActivityManager on display=3 → ru.yandex.yandexmaps [ClusterService] startActivityViaIAM → fallback context: null [ClusterService] Global launch error for ru.yandex.yandexmaps [SecurityException: Permission Denial: starting Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10008000 pkg=ru.yandex.yandexmaps cmp=ru.yandex.yandexmaps/.SplashScreen } from ProcessRecord{... com.byd.dashcast/u0a148} (pid=9647, uid=10148) with launchDisplayId=3] Our app's uid 10148 is denied cross-display launches by IActivityTaskManager on DiLink 5.0, even though INTERNAL_SYSTEM_WINDOW is granted at install time. This differs from DiLink 3.0 where the same typed-IATM reflection path works fine. The D31 diagnostic probe (build 186) had already validated the workaround: route the launch through ADB shell (uid 2000, which on this hardware has both the BYD auto_container whitelist entry and the unrestricted activity-launch privileges granted to the shell user). Build 188 ports that workaround into production. The same field log also revealed three wm overscan ... -> Unknown command: overscan errors. The wm overscan subcommand was removed in Android 11 (API 30) — DL5 is API 32, so every wm overscan shell call has been a no-op since 1.2.0. Build 188 stops issuing it on DL5. DL5 app launch — ADB shell route AdbLocalClient.isDiLink5Safe(Context) — visibility bumped Previously private static. Now package-private so ClusterService (same package com.byd.dashcast) can reuse the existing exception-safe DL5 detector instead of duplicating the try/catch around Platform.get().isDiLink5(ctx). Behavioural contract unchanged: returns true only when both Context != null and Platform.isDiLink5(ctx) returns true; any reflection failure returns false. Helper still cannot throw. New ClusterService.startActivityViaShell(packageName, displayId, launchIntent) String component = null; if (launchIntent != null && launchIntent.getComponent() != null) { ComponentName cn = launchIntent.getComponent(); component = cn.getPackageName() + "/" + cn.getClassName(); } if (component == null) { AppLogger.e(TAG, "startActivityViaShell: cannot resolve component for " + packageName); return; } final String cmd = "am start --display " + displayId + " -a android.intent.action.MAIN -c android.intent.category.LAUNCHER" + " -n " + component + " --activity-clear-task 2>&1"; AppLogger.i(TAG, "DL5 launch via shell: " + cmd); AdbLocalClient.executeShellWithResult(this, cmd, new AdbLocalClient.Callback() { @Override public void onSuccess(String out) { AppLogger.i(TAG, "DL5 am start → " + out.trim()); } @Override public void onError(String err) { AppLogger.e(TAG, "DL5 am start ERROR: " + err); } }); Component resolution reuses the getLaunchIntentForPackage(pkg) machinery that was already populating the Intent upstream — no new PackageManager queries, no new failure modes. Command pattern is bit-identical to the D31 diagnostic probe that PASSed end-to-end on the DL5 testeur on 22/05/2026. Dispatch via AdbLocalClient.executeShellWithResult: the existing dadb-based shell relay (with cached ADB key pair already accepted by the device) runs on the background executor. Errors are logged via AppLogger.e but not propagated to the LaunchCallback — consistent with the legacy IATM path which also reports onResult(true) after best-effort dispatch (the typed reflection path's RemoteException branches are swallowed there too). launchOnDashboard() — DL5 branch Right before the legacy startActivityViaIAM(launchIntent, opts) call, the method now tests AdbLocalClient.isDiLink5Safe(this): launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); ActivityOptions opts = ActivityOptions.makeBasic(); opts.setLaunchDisplayId(displayId); if (displayId > 0) applyClusterFreeformBounds(opts, displayId, packageName); if (AdbLocalClient.isDiLink5Safe(ClusterService.this)) { startActivityViaShell(packageName, displayId, launchIntent); } else { startActivityViaIAM(launchIntent, opts); } The applyClusterFreeformBounds() call still runs upstream and still applies setLaunchWindowingMode(FREEFORM) + setLaunchBounds(...) to the ActivityOptions — these are inert on DL5 (the shell am start ignores ActivityOptions entirely) but cost nothing to compute, and they remain necessary for the DL3 path which falls through to startActivityViaIAM unchanged. launchOnDashboardWithBounds() — DL5 branch Same pattern as launchOnDashboard(): on DL5 the shell path takes over, on DL3 the typed IATM path runs unchanged. Caveat: the explicit split-mode bounds passed by the caller (Rect left/top/right/bottom) are dropped on DL5 since am start does not accept windowing bounds. Split mode on DL5 would need a different approach (e.g. two shell launches on display #3 and #4 simultaneously, one app per physical PRESENTATION display) — out of scope for 1.2.2, will be addressed once single-app projection is field-validated. wm overscan — skipped on DL5 wm overscan was removed from WindowManagerService.ShellCommand in Android 11 (API 30) — its semantics were absorbed by wm display-add-state and DisplayInfo overrides. DL5 is API 32, so the command has been silently failing since 1.2.0. The field log confirmed: [AdbLocalClient] executeShellWithResult: wm overscan 80,50,80,50 -d 3 -> Unknown command: overscan (three times in the build 187 log: lines 31.016, 38.820, 46.264) Build 188 gates all three call sites on !isDiLink5Safe(this): applyClusterFreeformBounds() (per-launch overscan to apply app-specific insets). On DL5 logs DL5: skipping app-specific wm overscan (cmd removed in API 30+) at DEBUG level. onDashboardDisplayConnected() (display-level overscan applied once when the cluster display first comes up). Same log line at DEBUG. stopProjectionNoAdb() (reset overscan to 0,0,0,0 on display id 1 when teardown is requested via the BYD restore path). Bonus side-fix: the reset was hardcoding -d 1 (DL3 cluster display id) even on DL5 where the cluster lives at id 3 — was already producing the same Unknown command: overscan error. Now skipped entirely on DL5. On DL3 every overscan path is unchanged — DL3 still gets wm overscan H,V,H,V -d <displayId> applied at display-connect and at each app launch, plus the wm overscan reset -d 1 on stop. DiLink 3 — behavioural contract Every DL3 code path is bit-for-bit identical to build 187: launchOnDashboard() and launchOnDashboardWithBounds() — the DL5 branch is gated on isDiLink5Safe(this) which returns false on DL3, so the existing startActivityViaIAM(launchIntent, opts) typed reflection path runs unchanged with the same ActivityOptions (display id, freeform mode, launch bounds). applyClusterFreeformBounds() — DL3 still emits wm overscan H,V,H,V -d <displayId> at every launch and logs Applied app-specific wm overscan during launch on display N. onDashboardDisplayConnected() — DL3 still emits the display-level overscan and logs wm overscan applied on display N inset=H,V. stopProjectionNoAdb() — DL3 still emits wm overscan reset -d 1. The DL5 platform detection is wrapped in the same try/catch the rest of the codebase uses (isDiLink5Safe()), so any reflection failure on Platform.get().isDiLink5(ctx) falls back to the DL3 path — the production hot path can never throw because of platform detection. Build versionCode 187 → 188 versionName 1.2.1 → 1.2.2 (second 1.2.x minor bump in 24 hours, reflecting the iterative field-driven DL5 enablement) Branch beta/1.2.0-dilink5 — no merge into main per release policy Tag v1.2.2 Pre-release status Files touched MyBYDApp/app/build.gradle — version bump (187 → 188, 1.2.1 → 1.2.2) MyBYDApp/CHANGELOG.md — build 188 entry at top of Pre-releases MyBYDApp/app/src/main/java/com/byd/dashcast/AdbLocalClient.java — isDiLink5Safe visibility bump (private static → package-private) MyBYDApp/app/src/main/java/com/byd/dashcast/ClusterService.java — new startActivityViaShell() helper, DL5 branches in launchOnDashboard() and launchOnDashboardWithBounds(), DL5 guards on the three wm overscan shell call sites No new dependencies, no new permissions, no protocol changes, no new resources, no new strings, no new layout files. Validation plan DiLink 5 testeur device Cold-reboot to ensure XDJAScreenProjection_* PRESENTATION displays are up at id 3 and id 4. Install DashCast-v1.2.2-debug.apk. Open the dashboard, tap a target app — Yandex Maps, Huawei Maps, Waze, Standard ADB, or any non-system package declaring a LAUNCHER activity. Expected logcat sequence (tags ClusterManager + ClusterService + AdbLocalClient): DL5 activation path: sendInfo(16) only on auto_container sendInfo ADB(1000,16) → Result: Parcel(00000000 00000001 '........') DL5 cluster display ready: id=3 name=shared_fission_bg_XDJAScreenProjection_0 Cluster display connected: id=3 Cluster dimensions: 1920x720 displayId=3 DL5: skipping display-level wm overscan (cmd removed in API 30+) DL5 launch via shell: am start --display 3 -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n <pkg>/<launcher activity> --activity-clear-task 2>&1 executeShellWithResult: am start --display 3 ... -> Starting: Intent { ... } DL5 am start → Starting: Intent { ... } Visual check: the selected app appears on the cluster display. Absence check: no more SecurityException: Permission Denial ... with launchDisplayId=3 and no more Unknown command: overscan in the logs. Press Stop Projection. Expected: service call auto_container 2 i32 1000 i32 18 s16 "" 2>&1 ← from build 187 service call auto_container 2 i32 1000 i32 0 s16 "" 2>&1 ← from build 187 (no wm overscan reset line — gated out on DL5) Optional: Diag → ADAS tab → confirm pill still reads auto_container, both buttons still work (no regression on build 187 features). DiLink 3 reference device (regression check) Install DashCast-v1.2.2-debug.apk on a known-good DL3 cluster device. Trigger Start Projection. Expected logcat sequence unchanged from build 187: activation ADB(cmd=30): ... activation ADB(cmd=16): ... activation ADB(cmd=35): ... Launching via IActivityManager on display=N → <pkg> Applied app-specific wm overscan during launch on display N wm overscan applied on display N inset=H,V Regression markers — any of these lines in DL3 logs is a bug: DL5 launch via shell: (DL5 branch should not trigger) DL5: skipping ... wm overscan (cmd removed in API 30+) (DL5 guard should not trigger) DL5 activation path: (DL5 short-circuit from build 187 should not trigger) ADAS panel pill must still read AutoContainer (PascalCase, no underscore — fixed in 1.2.1) and both Afficher ADAS (12) / Masquer ADAS (13) must still light up the lamps. Telegram Post the DashCast-v1.2.2-debug.apk link plus a short note pointing field testers to the DL5 visual check (step 5 above). LinksAPK download GitHub release page
-
DashCast — BYD Cluster Launcher & Mirror
DashCast v1.2.1DashCast 1.2.1 (build 187) — DiLink 5 production projection Pre-release on branch beta/1.2.0-dilink5. Not merged into main. versionCode 186 → 187 — the car's package manager will detect this APK as a regular update of any previously installed DashCast 1.2.0 build. This build is the first production-side integration of the DiLink 5 projection sequence that the D12 and D31 diagnostic probes had only validated shell-side in build 186 (see field log byd_report_20260522_125322.log). Start Projection and Stop Projection now route through the correct AutoContainer flavour depending on the detected platform — DiLink 3 keeps the existing field-tested sequence bit-for-bit, DiLink 5 gets the minimal validated sequence. Platform dispatch — service-name routing DiLink 3 and DiLink 5 expose the cluster control binder under different names. Build 187 introduces a centralised, exception-safe dispatcher in AdbLocalClient: Platform Service name Confirmed by DiLink 3 (BYD Seal EU / Atto 3 / …) AutoContainer (PascalCase) User on production cluster, 22/05/2026 DiLink 5 (BYD-AUTO API 32) auto_container (snake_case) D11 / D12 / D31 PASS, field log 22/05/2026 New helpers in AdbLocalClient: autoContainerSvcName(Context) — returns the right name, never throws (any reflection failure on Platform.get().isDiLink5(ctx) falls back to the DL3 default). isDiLink5Safe(Context) — boolean wrapper with the same exception contract, used for gating the typed-daemon path. Three production call sites refactored to consume the helper instead of the previously hardcoded "AutoContainer" literal: AdbLocalClient.sendInfo() — the central service call … relay used by every callsite that ships an info code to the cluster. AdbLocalClient.restoreBydOnCluster() — Stop Projection (force-stop target + sendInfo(18) + sendInfo(0)). AdbLocalClient.restoreOriginCluster() — Stop Projection + restore original screen size (the sendInfo(<screenSizeCmd>) tail is a harmless no-op on DL5 since slot 30 is empty in the _Di5 clusterdebug enum). Typed daemon path — gated on DL3 BetaProxyClient.autoContainerSendInfo() resolves the binder via ServiceManager.getService("AutoContainer") — hardcoded in Phase4Verbs.AUTOCONTAINER_SVC. That name does not exist on DiLink 5, so every typed transact(2, …) would fail with IllegalArgumentException and force an extra round-trip before falling back to the shell relay. Build 187 short-circuits the typed path on DL5 in all three sites that previously gated it on BetaConfig.isProxyDaemonEnabled(context) alone: if (BetaConfig.isProxyDaemonEnabled(context) && !isDiLink5Safe(context)) { // typed daemon path — DL3 only } // shell fallback — picks svc name via autoContainerSvcName(context) DiLink 5 always goes straight to the shell with service call auto_container 2 …. DiLink 3 behaviour is unchanged: typed path tried first, shell fallback on failure with the correct AutoContainer name. Phase4Verbs itself is not touched in this build — the typed daemon remains a DL3-only optimisation. Migrating it to a DL5-aware variant (probing both AutoContainer and auto_container at startup with a cached IBinder) is out of scope for 1.2.1 because the shell path on DL5 is already fast enough (no service binary fork on DL5 — auto_container returns the Parcel in ~5 ms). Start Projection — ClusterManager.activateClusterDisplay() New short-circuit branch at the top of activateClusterDisplay() for DiLink 5: if (isDiLink5Safe()) { AppLogger.i(TAG, "DL5 activation path: sendInfo(16) only on auto_container"); AdbLocalClient.sendInfo(mContext, CLUSTER_TYPE, CMD_PROJECTION_ON, "", new AdbLocalClient.Callback() { @Override public void onSuccess(String out) { AppLogger.i(TAG, "DL5 activation ADB(cmd=16): " + out); mHandler.postDelayed(() -> resolveDl5Display(dm, callback), 500); } @Override public void onError(String err) { AppLogger.e(TAG, "DL5 activation ADB(cmd=16) ERROR: " + err); mHandler.postDelayed(() -> resolveDl5Display(dm, callback), 500); } }); return; } What changed compared to the DiLink 3 sequence: No sendInfo(30) (screen-size hint). Slot 30 is empty in the _Di5 variant of the clusterdebug enumeration documented in build 184 — the DL5 cluster Qt code never reads it. Sending it would just be a wasted round-trip. No sendInfo(35) (Di4.0 mode / VirtualDisplay creation). On DL3 this is what triggers AutoDisplayService.createVirtualDisplay() and the fission_bg_xdjaVirtualSurface PRESENTATION display appears ~280 ms later. On DL5 the equivalent XDJAScreenProjection_0 / XDJAScreenProjection_1 displays already exist at boot as persistent PRESENTATION displays (confirmed in D2 / D21 / D31 of the 22/05/2026 field log), so sendInfo(35) is unnecessary and would target an empty slot. Only sendInfo(16). This is the projection-ON trigger — the same code that D12 and D31 already validated end-to-end on the DL5 testeur device. 500 ms settle delay before resolving the display. In practice the displays are already up; the delay exists to absorb the rare case where the cluster Qt side needs a frame to react to sendInfo(16). Direct user constraint from the task brief: Le mieux maintenant c'est de câbler Start Projection pour Dilink5 qui fait uniquement la command 16 pour activer la projection avec le bon auto_container. Display targeting — resolveDl5Display() New private helper on ClusterManager. Reuses the existing findClusterDisplay(dm) which already returns the first non-default PRESENTATION display — so on the DL5 testeur device this naturally picks display id 3 (shared_fission_bg_XDJAScreenProjection_0). Display id 4 (_1) is left available for a future second-screen use. On miss, the helper polls every 250 ms for up to 3 s before calling onDisplayTimeout() — a defensive safety net that should never trigger on DL5 since the field log confirmed both XDJAScreenProjection_* displays are PRESENTATION-categorised and present from boot. Direct user constraint: on vient diffuser les applications lancé sur le display ID3 déjà pour commencer. findClusterDisplay() and isClusterDisplay() themselves are unchanged — they already accept any non-default display, so the DL5 displays #3 and #4 qualify out of the box. Stop Projection — automatic via existing path restoreBydOnCluster() was already issuing exactly the sequence the user asked for on DL5: force-stop <target package> sendInfo(1000, 18, "") // close projection sendInfo(1000, 0, "") // refresh Qt video stream The only concern on DL5 was the service name — fixed in section "Platform dispatch" above. No changes to the orchestration. Same goes for restoreOriginCluster(), where the optional trailing sendInfo(<screenSizeCmd>) is a harmless no-op on DL5 (empty slot 30 in _Di5, service call returns an empty Parcel without raising). Direct user constraint: Il faut aussi cabler le arreter projection qui fait les commandes 18 et 0 toujours avec le bon auto_container. Diagnostics — ADAS panel service-name fix DiagActivity.autoContainerSvcName() had shipped a wrong DL3 default since build 184: it returned "Auto_container" (PascalCase with underscore) — a name that does not exist on either platform. The ADAS quick-toggle panel was therefore silently issuing service call Auto_container 2 … on DL3, which the cluster ignores. Build 187 fixes the default to "AutoContainer" (PascalCase, no underscore) — the name the user confirmed in production on DL3 on 22/05/2026. DL5 keeps "auto_container" (snake_case). The pill displayed at the top of the ADAS panel now reads the correct service name on both platforms, and the Afficher ADAS (12) / Masquer ADAS (13) buttons send service call AutoContainer 2 i32 1000 i32 12 s16 "" on DL3 and the snake_case equivalent on DL5. DiLink 3 — behavioural contract Every DiLink 3 code path is bit-for-bit identical to build 186: The new DL5 branch in ClusterManager.activateClusterDisplay() is the very first statement of the method and is gated on isDiLink5Safe(). On DL3 it short-circuits to false and the existing 30 → 6 s → 16 → 6 s → 35 sequence runs unchanged, including every onError fallback branch. AdbLocalClient.sendInfo() typed-daemon path still runs first on DL3 (the isProxyDaemonEnabled && !isDiLink5Safe guard collapses to isProxyDaemonEnabled on DL3 since the negation is true). The shell fallback still emits service call AutoContainer 2 … on DL3 because autoContainerSvcName(ctx) returns "AutoContainer" when Platform.isDiLink5(ctx) is false. restoreBydOnCluster() / restoreOriginCluster() shell paths same story — AutoContainer on DL3, auto_container on DL5. User constraint explicitly stated in the task brief: Par contre on ne doit pas casser la partie Dilink3. Build versionCode 186 → 187 versionName 1.2.0 → 1.2.1 (first 1.2.x minor bump since the DL5 work started — reflects the production-side wiring being now in place) Branch beta/1.2.0-dilink5 — no merge into main in this build per the user's release policy Tag v1.2.1 Pre-release status — should be installed on testeur devices first Files touched MyBYDApp/app/build.gradle — version bump MyBYDApp/CHANGELOG.md — build 187 entry at the top of Pre-releases MyBYDApp/app/src/main/java/com/byd/dashcast/AdbLocalClient.java — helpers + 3 shell call-site refactors + 3 typed-daemon gates MyBYDApp/app/src/main/java/com/byd/dashcast/dashboard/ClusterManager.java — DL5 short-circuit + isDiLink5Safe helper + resolveDl5Display helper MyBYDApp/app/src/main/java/com/byd/dashcast/DiagActivity.java — ADAS DL3 service-name fix No new dependencies, no new permissions, no protocol changes, no new resources, no new strings. Validation plan DiLink 5 testeur device Cold-reboot to ensure XDJAScreenProjection_* PRESENTATION displays are up. Install DashCast-v1.2.1-debug.apk. Open the dashboard, select a target app (Waze, com.github.standardadb, or any non-system package declaring a LAUNCHER activity). Press the existing Start Projection control. Expected logcat sequence (tag ClusterManager + AdbLocalClient): DL5 activation path: sendInfo(16) only on auto_container sendInfo ADB: service call auto_container 2 i32 1000 i32 16 s16 "" 2>&1 DL5 activation ADB(cmd=16): Result: Parcel(...) DL5 cluster display ready: id=3 name=shared_fission_bg_XDJAScreenProjection_0 The existing app-launch path (which already uses ActivityOptions.setLaunchDisplayId) takes over and the app should appear on the cluster. Press Stop Projection. Expected: service call auto_container 2 i32 1000 i32 18 s16 "" 2>&1 service call auto_container 2 i32 1000 i32 0 s16 "" 2>&1 Open Diag → ADAS tab → confirm the pill reads auto_container. Press Afficher ADAS (12) → warning lamps should appear on the cluster. Press Masquer ADAS (13) → they should disappear. DiLink 3 reference device (regression check) Install DashCast-v1.2.1-debug.apk. Trigger Start Projection on a configured DL3 cluster. Expected logcat sequence unchanged from build 186: activation ADB(cmd=30): ... activation ADB(cmd=16): ... activation ADB(cmd=35): ... The shell path should consistently emit service call AutoContainer 2 … (PascalCase, no underscore) — any Auto_container or auto_container is a regression. Open Diag → ADAS tab → confirm the pill reads AutoContainer. Press the two buttons and verify the cluster reacts. Telegram channel Post the DashCast-v1.2.1-debug.apk link plus a one-line summary so testers can sideload. LinksAPK download GitHub release page
-
DashCast — BYD Cluster Launcher & Mirror
DashCast v1.2.0-build186DashCast 1.2.0 — build 186 (DiLink 5 enablement) Pre-release on the beta/1.2.0-dilink5 channel. Two scope items on top of build 185, both rooted in the 22/05/2026 DL5 field log (byd_report_20260522_115956.log) and the user's live testing on the DL5 vehicle: D10 APK extraction relocated to a user-visible destination, and a new D31 end-to-end cluster projection probe driven by com.byd.clusterdebug. This build is detected by the in-car OTA channel as an update over build 185. No production hot path was changed — projection lifecycle, ClusterManager, AdbLocalClient, daemon code are bit-for-bit identical to build 185. Everything in this build is opt-in via Diag → DiLink 5. D10 — APK extraction relocated to Download/dashcast_apks/ Field-tester feedback on build 185. The previous output dir was effectively invisible: getExternalFilesDir(null)/extracted_apks/ — app-private external storage, not surfaced by the BYD file manager without root. User saw "PASS, 7 APKs ready" in the report but had no way to grab the files without adb pull from a PC. Worse: every re-run added duplicate-or-cached copies into a dir the user could never empty by hand → slowly ate internal storage build after build. Fix. All filesystem ops in runD10 now go through the ADB shell (uid 2000, which on Android 10+ has unrestricted write access to /sdcard/Download/). mkdir -p /storage/emulated/0/Download/dashcast_apks at start-up. Cache check via stat -c %s '<dst>' (avoids relying on the app uid being able to read() the destination on A10+ scoped storage). Copy via cat '<src>' > '<dst>' && stat -c %s '<dst>'. At the very start of each runD10, the legacy getExternalFilesDir(null)/extracted_apks/ is wiped via rm -rf so the next install no longer accumulates dead bytes. PASS message now reads N APK(s) in Download/dashcast_apks/ (X cached), Y skipped. User-visible result. Destination is browsable by the stock BYD file manager (visible under Download → dashcast_apks). Removes the adb pull requirement entirely for users without a PC. Re-runs are zero-shell for already-extracted APKs (cache hit on versionCode embedded in the filename). D31 (new) — Live cluster projection end-to-end probe First test that drives the full visual cycle the user has been doing by hand to confirm DL5 projection works. For each DISPLAY_CATEGORY_PRESENTATION display (DL5 exposes #3 and #4 as XDJAScreenProjection_0/1): Step Action Why a sendInfo(1000, 16) Opens the projection tunnel; the shared_fission_bg_XDJAScreenProjection_* layerStack becomes visible (initially black — this is what the user confirmed seeing during D28 of build 185). b wait 1.5 s Lets Qt reposition the overlay. c am start --display N -n <pkg>/<launcher> Pushes a real activity onto the projection layerStack so the cluster shows the app, not just a black window. d wait 3 s User observes the cluster — this is the user-visible window. e dumpsys activity activities filtered on the package Snapshot of where the activity landed; correlates the visual with the stack state. f am force-stop <pkg> Clean release of the activity. g sendInfo(1000, 18) Closes the projection tunnel. h sendInfo(1000, 0) Restores native cluster video flow. i wait 1.5 s Visual separation between iterations. Target package — clusterdebug by default D31 targets com.byd.clusterdebug by default — BYD's own cluster diagnostic app, guaranteed to be installed on every DL5 head unit and designed to render on the cluster. The D8 dropdown at the top of the DL5 panel is an optional override: pick another installed package and D31 will use that one instead. PackageManager.getPackageInfo(pkg, 0) is called up front and FAILs cleanly with a clear message before any cluster touch if the package isn't installed. The report header now shows (default clusterdebug) or (D8 dropdown override) so you know exactly which path ran. Launcher resolution Reuses the existing resolveLauncherComponent(ctx, pkg) helper so am start --display N -n <ComponentName> always targets the package's declared LAUNCHER activity, whatever its concrete class name is. FAILs with Could not resolve a launcher activity for '<pkg>' if the package declares no launcher (rare — typically a service-only package). Status semantics PASS — every am start --display returned without error / SecurityException / Permission Denial. Message reads Cycle run on N display(s) — user to confirm visual rendering to keep the final go/no-go in the user's hands (this is a visual test; the shell can only say the API accepted the call, not that pixels actually reached the cluster). WARN — partial success. FAIL — every launch was refused. The full per-display sequence (commands + outputs + activity-stack snapshot) is dumped to r.detail so the user can read the report to understand which display did or did not light up. Cleanup guarantee D31 leaves the cluster in a clean state regardless of failure: am force-stop + sendInfo(18) + sendInfo(0) are always issued at the end of each per-display iteration, including on partial failure. Behavioural contract Production hot path (projection lifecycle, ClusterManager, AdbLocalClient, daemon code) is unchanged. The new D31 sequence is opt-in (Diag → DiLink 5 → tap D31 or Run all). DL5 test catalog grows from 30 to 31 entries — surfaced automatically by the existing prepareDl5TestRows() / bindDl5Row() UI code; no layout, strings, drawables, dependencies, or permissions added. DL3 / DL5 / DL2 production paths are bit-for-bit identical to build 185. Validation plan Sideload or OTA install on a DL5 vehicle. Open Diag → DiLink 5. Run D9 (discovery) then D10 → verify the 7 BYD APKs land in Download/dashcast_apks/ and that the BYD file manager sees them. Run D31 → the cluster should: Black out (sendInfo 16) for ~1.5 s. Display the clusterdebug UI for ~3 s on each PRESENTATION display. Black out again as sendInfo(18) / sendInfo(0) tear it down. Copy the report (Envoyer (.log)) and share for triage. Build versionCode 185 → 186 versionName pinned "1.2.0" per channel policy Touched files outside CHANGELOG / gradle: dilink5/DiLink5TestRunner.java (D10 body rewritten to shell-based ops + legacy dir cleanup, D31 added at the end of the file, catalog +1 entry, dispatcher +1 case). No new dependencies, drawables, strings, layouts, permissions, or protocol verbs. Download The signed debug APK is attached below: DashCast-v1.2.0-debug.apk. LinksAPK download GitHub release page
-
DashCast — BYD Cluster Launcher & Mirror
DashCast v1.2.0-build185DashCast v1.2.0-build185 — DiLink 2 reconnaissance + horizontal swipe in Diag Pre-release on branch beta/1.2.0-dilink5. No merge to main. What's new 1. New DiLink 2 reconnaissance diagnostic tab A complete recon test suite (L1–L15, 15 tests) dedicated to the DiLink 2 / Android 9 platform (alps / k65v1_64_bsp / MediaTek MT6765 / API 28). The runner is built on a completely different foundation than the DL5 suite because the DL2 environment has no working ADB-over-TCP on 127.0.0.1:5555 (confirmed ECONNREFUSED in two field reports), so the entire AdbLocalClient shell stack is unreachable. Every probe in this suite uses Java reflection or pure-Java APIs callable from the app process — no shell at all: ID Test Mechanism L1 Platform fingerprint Build.* + 26 selected ro.* / persist.sys.* / sys.* props via reflected SystemProperties.get L2 ADB local TCP ports probe Raw Socket.connect(127.0.0.1, port, 200ms) on 5037 / 5554 / 5555 / 5556 / 4444 L3 Multi-display reflective scan DisplayManager.getDisplays() + reflective DisplayManagerGlobal.getDisplayIds() (may surface IDs hidden from PRESENTATION enumeration) L4 SurfaceFlinger physical displays via IBinder ServiceManager.getService("SurfaceFlinger") + getInterfaceDescriptor() — deliberately no custom transact to avoid AOSP-fork SIGSEGV L5 DRM / framebuffer inventory /sys/class/drm, /dev/graphics, /sys/class/graphics via java.io.File (fb1/fb2 or multiple DRM connectors = hardware second display) L6 ServiceManager full inventory + filter Reflective ServiceManager.listServices() filtered on cluster|display|secondary|byd|alps|auto|mirror|magic|window|fission|xdja|projection|cross L7 Cluster service brute probe ServiceManager.getService() on 33 candidate names spanning DL3/DL5/MTK/alps conventions (cluster, byd_cluster, Auto_container, auto_container, mtk_cluster, alps_cluster, secondary_display, magicwindow, crosscontrol, xdja_container, byd_carapi, etc.) — for each present binder also dumps the AIDL interface descriptor L8 IActivityManager method enumeration Reflection on android.app.IActivityManager (NOT IATM — API 28 has IAM only) filtered on movetask|setlaunch|startactivity|moveactivitytask|settaskwindowing|getfocusedstack|attachapplication with full signatures L9 ActivityOptions.setLaunchDisplayId smoke Pure availability check (already confirmed present on DL2) L10 BYD packages dynamic scan PackageManager.getInstalledPackages(0) filtered on com.byd.* / com.alps.* / com.xdja.* / *cluster* / *dilink* with version + APK path L11 com.byd.cluster manifest deep dive Activities, services, receivers, providers, declared and requested permissions L12 com.byd.appstartmanagement manifest Same as L11 (DL2 has v1.0 vs DL5 v1.5+) L13 BYD SDK classpath probe Class.forName + reflective getInstance() on 8 BYDAuto*Device classes (Speed / Energy / Gearbox / AC / AirConditioner / Door / Light / Wiper) L14 /proc cluster process scan new File("/proc").listFiles() then <pid>/cmdline filtered on cluster|fission|projection|secondary|surface|display L15 Hidden-API reachability sanity ServiceManager.getService + descriptor on 6 core binders (window, activity, package, display, input, power) All exceptions are caught and reported as FAIL with <ExceptionClass>: <message>; a Throwable catch in the dispatcher ensures one test crashing never breaks the suite. Each test is sub-second on average — the whole 15-test pass completes in ~3 s. 2. Horizontal swipe navigation in Diag With 9 tabs (Cluster / Display / ADB local / Système / ADAS / Beta Engine / DiLink 5 / DiLink 2 / Sniffer) the tab bar is getting long. Build 185 adds a GestureDetector.SimpleOnGestureListener on the diag content frame: Clear horizontal fling (|dx| ≥ 120 px AND |dx| ≥ 1.5×|dy| AND |vx| ≥ 220 px/s) selects the next or previous tab Swipe left → next tab, swipe right → previous tab, clamped to [0, 9) The touch listener returns false so all child scrollers (NestedScrollView, RecyclerView test lists, scrollable SysInfo report) keep working untouched Reuses the existing OnTabSelectedListener (tabs.getTabAt(next).select()) so the same showPanelForTab code path runs — zero regression risk on the existing tab-click flow No animated slide between panels for now (the bascule is dry, like the current tab click); a proper ViewPager2 + FragmentStateAdapter refactor with animated slide is planned for a later build Wiring summary New file dilink2/DiLink2TestRunner.java (~580 LoC, self-contained — no shared types with DL5 / Beta runners) New layout res/layout/include_diag_dilink2.xml New tab TAB_DILINK2 = 7 in DiagActivity.java (Sniffer shifted from 7 → 8); new panelDl2 field; new bindDl2Panel() / prepareDl2TestRows() / bindDl2Row() / runDl2AllTests() / updateDl2Counters() / copyDl2Report() mirror the DL5 lifecycle with their own state lists (dl2RowViews, dl2LastResults) — no state collision with DL5 or Beta New attachSwipeNavigation(View) method called once in bindTabs() New <TabItem> and <include> in res/layout/activity_diag.xml 9 new FR strings in res/values/strings.xml (diag_tab_dilink2, diag_dl2_header_*, diag_dl2_pill_*, diag_dl2_counters_fmt) — the 12 locale files are intentionally NOT updated yet; will batch with the next translation pass once the DL2 panel labels stabilize after field testing Zero production-path changes No changes to ClusterManager, AdbLocalClient, Platform.java, BetaTestRunner, DiLink5TestRunner No new permissions, no new dependencies, no new protocol verbs DL3 and DL5 behaviour is bit-for-bit identical to build 184 DL2 signature detection in bindDl2Panel() is a UI-side hint only (reads Build.BRAND + Build.PRODUCT), no Platform.java extension Build metadata versionCode 184 → 185 versionName pinned "1.2.0" per channel policy Branch: beta/1.2.0-dilink5 (no merge to main) Tag: v1.2.0-build185 APK: DashCast-v1.2.0-debug.apk (14 MB) Validation plan DL2 device — open Diag, swipe (or tap) to the new DiLink 2 tab, confirm the signature pill reads Signature : DL2 détectée, press Lancer tous, wait ~3 s for the 15-test pass, press Envoyer (.log), share the report via Telegram. DL3 / DL5 device (regression check) — open Diag, swipe through all 9 tabs, confirm: No layout glitch, all panels render correctly DiLink 5 panel still functional (run a D1 / D11 / D23 quick sanity) ADAS panel still functional (Show / Hide buttons) Beta Engine panel still functional Sniffer panel still functional (start / snapshot / stop) The DL2 tab is visible too (we did NOT gate it behind a platform detection) — pressing Lancer tous on DL3/DL5 produces a report where L1 reports WARN ("not a DL2 signature") and the other tests still return useful data, confirming the suite is non-destructive on non-DL2 platforms. What this build will tell us about DL2 For the first time, the field report will answer: Whether any non-DisplayManager surface (SurfaceFlinger binder, DRM connectors, /sys/class/graphics) exposes the physically-present cluster screen Which binder services BYD or alps registered on this platform for cluster control What com.byd.cluster and com.byd.appstartmanagement actually contain and expose (entry points, permissions) Which IActivityManager methods are available for typed display routing on API 28 without IActivityTaskManager Whether the ADB-TCP situation can be unblocked from the device side Based on these answers, build 186 will design the actual DL2 cluster projection strategy. LinksAPK download GitHub release page
-
DashCast — BYD Cluster Launcher & Mirror
DashCast v1.2.0-build1841.2.0-build184 — DiLink 5 prerelease Continuation of the beta/1.2.0-dilink5 line. Built on top of build 183, focused on shipping the actionable findings from the first DL5 field log (byd_report_20260522_092905.log). New feature — ADAS quick-toggle diagnostic tab The previously unused Stress test tab is replaced by a dedicated ADAS tab in Diag (tab position #5). Two MaterialButtons wire the universal AutoContainer codes confirmed in clusterdebug v1.6.1.4: Show ADAS → sendInfo(12) (显示Adas) Hide ADAS → sendInfo(13) (关闭Adas) Runtime status pill shows the resolved service name: Auto_container (PascalCase) on DL3 / Di4, auto_container (snake_case) on DL5+. Service-name dispatch resolved per press via Platform.get().isDiLink5(ctx) — toggling the DL5 override in Settings is picked up without restarting Diag. Raw shell response (Parcel hex or error) is displayed in a monospace result card; error path prefixes with ERREUR:. Underlying command: service call <svc> 2 i32 1000 i32 <12|13> s16 "". DiLink 5 test suite polish (based on field-log feedback) D10 — APK extraction cache. Filename already embeds versionCode, so an existing non-empty destination file is now skipped (no cat redirect). New cached counter, log line ↻ <pkg> (cached, NN KB). Summary message reads N APK(s) ready (X cached), Y skipped — adb pull the dir. Second run on the same device produces zero shell calls when no APK version changed. D23 — rewritten as visual warning-lamp cycle. Build 183 looped over binder transaction codes 0..8 for API surface enumeration (redundant after the clusterdebug enumeration). D23 now sends sendInfo(2) (All warning lamps ON), waits 3 s, then sendInfo(3) (All warning lamps OFF). Status WARN with a message asking the user to visually confirm the lamps lit up for ~3 s. End-to-end validation of: (a) snake-case auto_container reaches the cluster on DL5, (b) sendInfo path is functional, (c) cluster Qt honours the published codes. D28 — dropped useless sendInfo(30) step. clusterdebug _Di5 variant has an empty slot at code 30 (was Set screen size on DL3 / Di4, dropped on DL5). New flow: logcat -c → sendInfo(16) (Qt standby ON) → 3 s → dumpsys display → filtered logcat (80 lines) → cleanup sendInfo(18) + sendInfo(0). Documentation New reference file doc_api/APK_com.byd.clusterdebug_CODES.md: full enumeration of com.byd.clusterdebug v1.6.1.4 (versionCode 10601004) sendInfo codes across all three infoListInit* variants (generic for DL3 / Di4, _Di5 for DL5, _R for R-series), side-by-side zh / EN translation for every populated slot. Canonical reference for ADAS panel codes (12/13) and D23 warning-lamp cycle (2/3); audit matrix for platform-stable vs platform-stripped codes. Behavioural contract Production hot path (ClusterManager.sendActivationSequence and full cluster lifecycle) unchanged. ADAS panel uses the existing AdbLocalClient.executeShellWithResult background path — no new thread, no new permission, no new system Context, no new protocol verb. Panel is read-only / write-only with respect to projection state: showing ADAS while projection is running just overlays the warning lamps; hiding ADAS does not affect projection. auto_container production fix (snake_case dispatch on DL5) still deferred pending full clusterdebug + containerservice APK reverse-engineering. Build versionCode 183 → 184, versionName pinned "1.2.0". No new dependencies, drawables (reuses ic_stethoscope), permissions, or protocol verbs. Touched files: DiagActivity.java, dilink5/DiLink5TestRunner.java, res/layout/activity_diag.xml, res/layout/include_diag_adas.xml (new), res/values/strings.xml, app/build.gradle, CHANGELOG.md. Validation plan On a DL3 device: open Diag → ADAS → confirm pill reads Auto_container, press Show ADAS (12) → expect warning lamps lit + Parcel response, press Hide ADAS (13) → expect lamps off. On a DL5 device: confirm pill reads auto_container (snake_case), repeat the cycle. On a DL5 device: Diag → DiLink 5 → run D10 twice → second pass shows all rows as cached; run D23 → confirm visual warning-lamp cycle WARN message; run D28 → confirm the report no longer contains a sendInfo(30) section. Toggle DL5 override in Settings on a DL3 device → reopen Diag → ADAS → confirm pill switches between Auto_container and auto_container and that subsequent button presses use the corresponding service name in the displayed $ command. LinksAPK download GitHub release page
-
Продажи нового кроссовера BYD Yuan Plus начались в Китае. Изучаем цены и характеристики
На рынке Китая начались продажи кроссовера BYD Yuan Plus нового поколения. Автомобиль с электрической силовой установкой предлагает выбор из четырёх комплектаций. Стоимость базовой версии составила 119 990 юаней (около 1,25 млн рублей). Новый электрический кроссовер BYD Yuan Plus является третьим поколением модели. Причем на этот раз смена генерации — это не просто очередной рестайлинг. Автомобиль стал заметно крупнее. Теперь его размеры составляют 4665 x 1895 x 1675 мм, а колёсная база — 2770 мм. Для сравнения, предыдущий Yuan Plus имел габариты кузова 4455 х 1875 х 1615 мм и базу 2720 мм. Электрический кроссовер BYD Yuan Plus третьего поколения Экстерьер новинки также претерпел значительные изменения. Обновились не только передняя и задняя части паркетника, но также кузовные панели, дверные ручки и даже форма задней оконной стойки. Кроме того, можно отметить, что зарядный порт нового BYD Yuan Plus переместился с правого переднего крыла на заднее. На крыше расположился сенсор LiDAR. А внутри появились новый двухспицевый руль, изменённая передняя консоль и переработанный разделительный тоннель. Электрический кроссовер BYD Yuan Plus третьего поколения Кроссовер построен на модульной архитектуре e-Platform 3.0 Evo. Его подвеска полностью независимая: передняя — McPherson, задняя — многорычажная система. Автомобиль получил коэффициент аэродинамического сопротивления (Cd) в 0,264. Салон рассчитан на 5 человек. А под капотом скрывается дополнительный багажный отсек объёмом 180 литров. Продажи BYD Yuan Plus: цены и характеристикиНовый BYD Yuan Plus представили в четырех комплектациях. Стоимость двух начальных версий составляет 119,9 и 129,9 тысячи юаней (1,25-1,35 млн рублей). Такой кроссовер оснащен задним электромотором мощностью 272 лошадиные силы. Тяговая батарея имеет емкость 57,54 кВт·ч. Она обеспечивает запас хода до 540 километров (по циклу CLTC). Разгон с нуля до 100 километров занимает 6,6 секунды. Максимальная скорость автомобиля составляет 190 километров в час. Электрический кроссовер BYD Yuan Plus третьего поколения Две более дорогие комплектации Yuan Plus стоят 142,9 и 149,9 тысячи юаней (1,5-1,56 млн рублей). Они также оснащены одним задним электромотором. Но его мощность выше — 326 лошадиных сил. А полного заряда батареи ёмкостью 68,54 кВт·ч хватает на 630 километров (CLTC). Разгон с места до 100 км/ч занимает 5,9 секунды. Максимальная скорость составляет 200 км/ч. Независимо от версии, все автомобили поддерживают сверхбыструю зарядку: за 5 минут батарея заряжается с 10 до 70%, а за 9 минут — с 10 до 97%. Подробнее об оснащенииБазовая комплектация нового BYD Yuan Plus включает 18-дюймовые диски, светодиодную оптику, обивку сидений из экокожи, систему камер кругового обзора с функцией «прозрачного шасси», бесключевой доступ, аудиосистему на 8 динамиков и климат-контроль с функцией очистки воздуха и фильтром PM 2.5. Интерьер кроссовера BYD Yuan Plus третьего поколения В салоне перед водителем расположена 8,8-дюймовая цифровая панель приборов. В центре передней консоли — 12,8-дюймовый сенсорный экран с новой системой DiLink. На разделительном тоннеле между передними сиденьями — пара подстаканников, беспроводная зарядка для смартфона (50 Вт) и минимальное количество физических кнопок. Селектор коробки передач находится на рулевой колонке. А передние кресла оснащены электрорегулировками, обогревом и вентиляцией. Интерьер кроссовера BYD Yuan Plus третьего поколения В свою очередь, максимальная комплектация кроссовера предлагает: 19-дюймовые колеса, адаптивную систему жесткости подвески (YunNian-C), активный воздухозаборник, электропривод двери багажника и увеличенный до 15,6 дюймов центральный сенсорный экран. Также у модели есть 12-дюймовый проекционный дисплей (HUD), панорамная крыша, встроенный холодильник, 16 динамиков аудиосистемы и 256-цветная атмосферная подсветка салона. Интерьер кроссовера BYD Yuan Plus третьего поколения Следует уточнить, что, кроме базовой комплектации, во всех версиях нового Yuan Plus установлена система автономного вождения DiPilot 100 с адаптивным круиз-контролем. Однако за доплату 12 тысяч юаней (около 125 тысяч рублей) этот комплекс можно «прокачать» до более продвинутой версии DiPilot 300 с сенсором LiDAR над лобовым стеклом. Алексей БОГАЧЁВ (@КитайАвто774)
-
DashCast — BYD Cluster Launcher & Mirror
DashCast v1.2.0-build1831.2.0-build183 — DiLink 5 reconnaissance probes (D21–D30) Branch: beta/1.2.0-dilink5 · NOT for merge to main. Prerelease intended for DiLink 5 field testers only. Why this build The first DL5 field log (build 182) revealed three open questions blocking projection: Service name mismatch. auto_container is reachable on DL5 (D11/D12 PASS via explicit snake_case) but our production code path hardcodes AutoContainer (PascalCase, DL3 convention) → all 17+ service call sites fail with Service AutoContainer does not exist. Fix is intentionally DEFERRED until the BYD APKs extracted by D10 are reverse-engineered offline (clusterdebug, containerservice, appstartmanagement, smarttravel, crosscontrol, providers.appstartup, car.server). Display #2 mystery. DL5 has 4 displays vs DL3's 3. Display #2 is not PRESENTATION-typed, holds a single window from PID 1326 / uid 10054 since boot. Is it the native cluster surface, the HUD, the mirror service? Windowing API absent. IATM.setTaskWindowingMode(int,int,boolean) doesn't exist on DL5's API 32 build — BYD either stripped it or renamed it. Build 183 ships 10 new diagnostic probes (D21–D30) designed to answer those questions when combined with the offline APK analysis. No production-path changes — read-only reconnaissance. New tests ID What Why D21 All displays full inventory (DisplayManager + reflective DisplayInfo) Captures non-PRESENTATION displays previously hidden by D2's filter (incl. mystery #2) D22 Display #2 owner identification (dumpsys display + SurfaceFlinger --display-id + ps -A) Identify what PID 1326 actually is D23 auto_container transaction codes scan (codes 0..8) Map the binder API surface beyond sendInfo (code 2) — find hidden capabilities D24 BYD-specific services probe (11 services: magicwindow, crossservice, mirror, BYDMgmt, etc.) Capture each interface descriptor — magicwindow is prime suspect for dual-screen API D25 IActivityTaskManager methods enumeration (reflection, HOT bucket = windowing/display/task) Find the actual replacement for the absent setTaskWindowingMode D26 am start --display N probe on each non-main display (force-includes id 2) Which displays accept arbitrary apps without sendInfo preparation? D27 BYD clusterdebug.MainActivity launch + logcat trace Watch BYD's own cluster diagnostic tool exercise the legitimate projection flow D28 Live logcat capture during sendInfo(30) → sendInfo(16) cycle Capture every BYD log line during a real projection-start attempt (with cleanup) D29 Projection-related intent filters discovery (9 candidate actions + containerservice/appstartmanagement intent-filter dump) Find the official entry-point intent for projection D30 SurfaceFlinger display topology (--display-id + filtered Layer/Display dump) Ground-truth physical/virtual display mapping (bypasses WindowManager filtering) What testers should do Install the OTA or sideload DashCast-v1.2.0-debug.apk manually. Reboot (ensures fresh state — D28 logcat capture is cleaner). Open DashCast → Diag → DiLink 5 → tap Run all → wait for the 30-test pass (~60 s). Tap Copy report → share the .log via Telegram. If you already extracted the 7 APKs from build 182, no need to re-extract — D9/D10 will just re-confirm them. The interesting new data is in D21–D30. What's still pinned versionName stays "1.2.0" (channel policy). Branch stays beta/1.2.0-dilink5 — NOT to merge to main until DL5 projection actually works on a real device. auto_container service-name fix and windowing API fix both intentionally deferred until APK RE + D21–D30 results are combined. Files app/build/outputs/apk/debug/DashCast-v1.2.0-debug.apk (~14 MB) Validation Pure reconnaissance build — no production behaviour change. If everything is broken on your DL3, that's a build regression — please report. LinksAPK download GitHub release page
-
BYD tops China EV “lawsuit leaderboard” with 1.02 million USD in blogger claims
Chinese automotive social media has recently circulated informal “lawsuit leaderboards” tracking legal claims filed by automakers against bloggers and self-media accounts accused of spreading false or defamatory EV-related information. Verified by CarNewsChina using public court filings, company statements, and legal notices. Among the brands discussed online, BYD ranked highest by disclosed claim value. According to court filings and public announcements, BYD has primarily sued seven blogger accounts, with cumulative claims totalling 6.9263 million yuan (1.02 million USD). Other major Chinese EV brands, including HIMA, Nio, and Xpeng, have also pursued lawsuits against content creators, though publicly disclosed compensation figures were lower. BYD leads by total claim value According to publicly compiled figures, BYD’s disclosed legal claims against bloggers totalled 6.9263 million yuan (1.02 million USD). Most of the BYD-related cases involved disputes over videos and social-media posts discussing Blade Battery safety, plug-in hybrid systems, battery durability, thermal management, or alleged vehicle quality concerns. Several court rulings addressed commercial defamation and unfair competition claims arising from allegedly fabricated or unverified information. One of the highest-profile cases involved the blogger account “Long Ge Jiang Dian Che” (龙哥讲电车), operated by a repair-shop content creator surnamed Liu. The account published videos discussing BYD’s Blade Battery and hybrid systems before becoming involved in multiple lawsuits filed by BYD, Aito (HIMA), and Xpeng. On May 16, the blogger published a public apology video after a second-instance court ruling involving BYD. The judgment ordered compensation of 2 million yuan (294,118 USD) and stated that several disputed claims constituted commercial defamation and unfair competition. Separate rulings and settlements involving the same blogger resulted in compensation payments to Aito (HIMA) and Xpeng. Automaker lawsuit leaderboard Other major Chinese EV brands, including HIMA, Nio, and Xpeng, have also pursued lawsuits against content creators, though publicly disclosed compensation figures were lower. RankAutomakerTotal Claim AmountBlogger / AccountClaim Amount1BYD1,018,574 USD (6,926,300 yuan)Da Qin Jun Shan Tuan (Yao Weiqiang)295,588 USD (2,010,000 yuan)Long Zhu – Ji Che296,868 USD (2,018,700 yuan)Long Ge Jiang Dian Che294,118 USD (2,000,000 yuan)Wang Wu Kong Shuo Che46,147 USD (313,800 yuan)Yi Lan Zhong Che46,147 USD (313,800 yuan)Diandongche Dao Bi Dao22,059 USD (150,000 yuan)Qiche Bao Guang Tai17,647 USD (120,000 yuan)2Aito (HIMA)302,941 USD (2,060,000 yuan)Wo Shi Da Bin Tong Xue (Da Bin)220,588 USD (1,500,000 yuan)Biao Qi Qi Wei – Song Ting Xian Sheng58,824 USD (400,000 yuan)Long Ge Jiang Dian Che23,529 USD (160,000 yuan)3Nio88,235 USD (600,000 yuan)Xiao Niu Shuo Che (Gu Yubo) [Banned]88,235 USD (600,000 yuan)4Stelato (HIMA)44,118 USD (300,000 yuan)Sai Che Xing Bing Le44,118 USD (300,000 yuan)5Xpeng14,706 USD (100,000 yuan)Long Ge Jiang Dian Che14,706 USD (100,000 yuan)Compiled by CarNewsChina Aito (HIMA) legal victories Aito (HIMA) secured a first-instance ruling in the case involving the blogger account “Wo Shi Da Bin Tong Xue” (Da Bin), where the court ruled on February 11, 2026 that the posts exceeded the boundaries of legitimate speech and demonstrated clear malicious intent. The ruling ordered damages of 1.50 million yuan (220,588 USD), according to ifeng. Nio legal victories Nio was involved in a long-running case against the blogger “小牛说车” (Gu Yubo), which resulted in a court order requiring a 90-day continuous public apology published on the account, alongside financial damages exceeding 600,000 yuan (88,235 USD). The account was later permanently banned across multiple platforms following the enforcement of the ruling, according to Sina. Xpeng legal ruling Xpeng won a first-instance judgment at the Guangzhou Internet Court on September 17, 2025, in a case involving the blogger “Long Ge” and the parent company Beijing Managedian Technology Co., Ltd. The court found that the account disseminated malicious, non-factual content presented as EV repair tutorials, thereby infringing Xpeng’s reputation rights. The ruling ordered the removal of defamatory content, a public apology, and compensation of 100,000 yuan (14,706 USD), according to Sina. Battery disputes and teardown controversies The lawsuits emerged alongside increasingly aggressive online debates surrounding EV batteries, charging systems, and teardown testing procedures. Recent discussions intensified after a Blade Battery teardown video showed a 170-cell battery pack dismantled following a 40-hour freeze test. Members of the technical team later defended the eight-hour teardown process after criticism spread online about the testing methodology and the conclusions of the battery analysis. These discussions are closely linked to broader commentary on the blogger account “Cai Shen Dao” (才神道), which has recently drawn attention in Chinese EV communities for publishing critical content on BYD battery technology and comparative analyses across battery manufacturers. Online allegations suggesting the account may represent or be affiliated with a competing battery supplier remain unverified and have not been confirmed in any court proceedings. Industry backdrop In the domestic EV market, Chinese automakers have increasingly pursued legal action against online misinformation, commercial defamation, and reputational disputes as competition intensified. Nio recorded domestic sales of 18,991 vehicles in April 2026, down 15.4% month-on-month and 1.4% year-on-year, according to China EV DataTracker data. March sales reached 22,437 vehicles, up 121.5% year-on-year. Nio’s sales in China til April 2026. Credit: China EV DataTracker