Przeglądaj źródła

fix: actually disable Impeller on Android

Flutter ignores io.flutter.embedding.android.EnableImpeller under <activity>; move the opt-out to <application> so CarDash uses Skia. This fixes the SwiftShader emulator segfault when opening Settings. Bump to 1.0.7+19, add a manifest regression test, and publish the APK.
Krasimir Kazakov 6 dni temu
rodzic
commit
d83bb26eec

+ 2 - 1
AGENTS.md

@@ -87,6 +87,7 @@ notifyListeners();
 - Only x86_64 system images run here; the pre-installed arm64 image cannot boot. The download script/images are already in `/opt/android-sdk`.
 - `QUERY_ALL_PACKAGES` is required to list every app. It is acceptable for a launcher but is not a Play-Store-distributable choice as-is; document it.
 - The Flutter template uses AGP 9.1 built-in Kotlin support. Do not add the Kotlin Gradle plugin unless the build explicitly requires it.
+- Impeller stays disabled through `io.flutter.embedding.android.EnableImpeller=false` in the **`<application>`** block. Flutter silently ignores this key under `<activity>`; putting it there enabled Impeller and crashed the SwiftShader emulator when Settings opened. Do not move it back.
 - Immersive mode is reapplied on every `resumed` lifecycle event; Android may restore system bars after dialogs or role prompts.
 - The HOME role must be granted for the dashboard to replace the tablet home screen. On the emulator use `cmd role add-role-holder`; the Settings row uses `RoleManager`.
 - There is no Home-role banner. The one-time first-run tip uses `LauncherController.homeRoleHintShown`; do not show it again.
@@ -129,4 +130,4 @@ Release checklist:
    selects the highest published semver tag, and downloads
    `/raw/main/releases/cardash-android-<version>.apk`. No app-side release configuration is needed.
 7. The current release build uses the project debug signing config. Reuse the existing debug keystore (certificate SHA-256 `C2:8E:5B:0B:E0:F8:27:EE:12:22:B0:62:9F:EE:1C:F2:7E:14:3F:78:95:86:1E:3F:34:15:A6:E5:3A:06:BE:ED`). It is stored locally at `android/keystore/debug.keystore`; that path is gitignored and must never be committed. The Gradle config uses it for debug and release when present, falling back to the default debug key otherwise. Never let Gradle generate a fresh key for a release, or in-place updates will fail. Android still requires the user to confirm installation and allow unknown-app installs once; never attempt silent installation.
-8. After the release APK is built, check whether `100.101.102.21:5555` is reachable (`/opt/android-sdk/platform-tools/adb connect 100.101.102.21:5555`). If it is, also install the release APK on that tablet with `adb -s 100.101.102.21:5555 install -r releases/cardash-android-<version>.apk`, following the two-attempt ADB rule. If it is unreachable, skip it silently; this step is optional and must never fail the release.
+8. After the release APK is built, check whether `100.101.102.21:5037` is reachable (`/opt/android-sdk/platform-tools/adb connect 100.101.102.21:5037`). If it is, also install the release APK on that tablet with `adb  -H 100.101.102.21 -P 5037 -s emulator-5554 install -r releases/cardash-android-<version>.apk`, following the two-attempt ADB rule. If it is unreachable, skip it silently; this step is optional and must never fail the release.

+ 5 - 4
README.md

@@ -18,7 +18,7 @@ the normal Android launcher mechanism. No overlays, no root, no kiosk setup.
 ## Download
 
 - [Gogs release page](https://git.krasi.net/kkazakov/car-interface-android/releases) — published releases
-- [cardash-android-1.0.6.apk](releases/cardash-android-1.0.6.apk) — universal Android release
+- [cardash-android-1.0.7.apk](releases/cardash-android-1.0.7.apk) — universal Android release
 - SHA-256 and build details: [releases/README.md](releases/README.md)
 
 Gogs limits release attachments to 32 MB, so the 54 MB universal APK is linked
@@ -335,10 +335,11 @@ Open-Meteo fallback were verified live on the API 35 emulator. Now-playing
 title, artist, progress and pause/resume were verified against a live Chrome
 MediaSession. The OBD hardware path compiles and is covered by the
 parser/economy design, but no ELM327 adapter is available on this machine;
-socket behavior still needs a car test. The 1.0.6 universal APK is built from
-this tree and published; devices on 1.0.5 will pick it up through
+socket behavior still needs a car test. The 1.0.7 universal APK is built from
+this tree and published; devices on 1.0.6 will pick it up through
 Settings → Updates when the release is published. The emulator has verified the
-in-app update path.
+in-app update path. 1.0.7 moves the Impeller opt-out into the manifest's
+`<application>` block; Flutter ignored it under `<activity>`.
 
 ## Troubleshooting the emulator
 

+ 7 - 6
android/app/src/main/AndroidManifest.xml

@@ -39,12 +39,6 @@
                 android:name="io.flutter.embedding.android.NormalTheme"
                 android:resource="@style/NormalTheme" />
 
-            <!-- Skia is more stable than Impeller on software-rendered and older
-                 car tablets; the UI is intentionally lightweight. -->
-            <meta-data
-                android:name="io.flutter.embedding.android.EnableImpeller"
-                android:value="false" />
-
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.HOME" />
@@ -76,6 +70,13 @@
         <meta-data
             android:name="flutterEmbedding"
             android:value="2" />
+
+        <!-- Skia is more stable than Impeller on software-rendered and older
+             car tablets. Flutter reads this key from <application>, not from
+             <activity>; putting it on the activity silently leaves Impeller on. -->
+        <meta-data
+            android:name="io.flutter.embedding.android.EnableImpeller"
+            android:value="false" />
     </application>
 
     <queries>

+ 6 - 1
docs/IMPLEMENTATION_PLAN.md

@@ -159,11 +159,12 @@ Android (Kotlin)
 | M20 | Fixed-volume car detection; relative volume fallback; 1.0.4 | Done |
 | M21 | On-screen volume diagnostics and stream snapshots; 1.0.5 | Done |
 | M22 | Bluetooth/OBD freeze hardening: UI-thread offload, device filtering, bounded retries, resume debounce; 1.0.6 | Done |
+| M23 | Move the Impeller opt-out into `<application>` so Skia is actually used; 1.0.7 | Done |
 
 ## 5. Verification checklist
 
 - [x] `flutter analyze` clean.
-- [x] Unit/widget tests pass (76 tests, including OBD device filtering, bounded retries and resume debounce).
+- [x] Unit/widget tests pass (77 tests, including OBD device filtering, bounded retries, resume debounce and the application-scoped Impeller opt-out).
 - [x] Debug APK builds with JDK 21.
 - [x] Installs on API 35 1280×800 tablet.
 - [x] HOME role granted; Home opens CarDash.
@@ -326,6 +327,10 @@ Android (Kotlin)
 - 2026-10-30: Added fixed-volume detection and a relative volume-step fallback
   for car units that ignore absolute `setStreamVolume`; fixed-volume sliders now
   explain that the vehicle amplifier owns the level. Bumped to 1.0.4+16.
+- 2026-10-31: Found the emulator's Settings crash was Impeller, not the app's
+  layout. The `EnableImpeller=false` opt-out had been placed under `<activity>`,
+  where Flutter silently ignored it; moved it to `<application>`, so 1.0.7
+  actually uses Skia. Bumped to 1.0.7+19.
 - 2026-10-31: Hardened the OBD-II/Bluetooth path after a freeze triggered by
   returning from Bluetooth settings. Bluetooth binder calls now run on a
   serialized native worker with timeouts; the picker filters to likely OBD-II

+ 1 - 1
lib/core/app_version.dart

@@ -1,2 +1,2 @@
 /// Keep this in sync with the `version` in pubspec.yaml.
-const String appVersion = '1.0.6';
+const String appVersion = '1.0.7';

+ 1 - 1
pubspec.yaml

@@ -1,7 +1,7 @@
 name: cardash_launcher
 description: "CarDash — a calm, configurable Android car dashboard launcher."
 publish_to: "none"
-version: 1.0.6+18
+version: 1.0.7+19
 
 environment:
   sdk: ^3.13.7

+ 16 - 0
releases/README.md

@@ -1,5 +1,21 @@
 # CarDash releases
 
+## 1.0.7 — Truly disable Impeller
+
+- **File:** `cardash-android-1.0.7.apk`
+- **Version:** 1.0.7 (versionCode 19)
+- **ABIs:** arm64-v8a, armeabi-v7a, x86_64
+- **SHA-256:** `5b37116b38683ae5e9c42f36841ca509a417f21164902ffe0a93e7b5b29c61ae`
+- **Signing:** project debug key matching all releases.
+- **Changes:** moves the `EnableImpeller=false` manifest entry from
+  `<activity>` to `<application>`. Flutter ignores the activity-scoped key, so
+  1.0.6 and earlier silently ran Impeller. That caused the host emulator's
+  SwiftShader process to segfault when Settings opened. 1.0.7 uses Skia as
+  originally intended. Includes all 1.0.6 Bluetooth/OBD hardening.
+- **Verified:** on the 1024×600 Android 14 SwiftShader emulator, opening
+  Settings, granting Bluetooth permission, and the Bluetooth-off → Android
+  settings → Bluetooth-on → return flow all stay alive.
+
 ## 1.0.6 — Bluetooth/OBD freeze hardening
 
 - **File:** `cardash-android-1.0.6.apk`

BIN
releases/cardash-android-1.0.7.apk


+ 1 - 0
releases/cardash-android-1.0.7.apk.sha256

@@ -0,0 +1 @@
+5b37116b38683ae5e9c42f36841ca509a417f21164902ffe0a93e7b5b29c61ae  releases/cardash-android-1.0.7.apk

+ 27 - 0
test/manifest_impeller_test.dart

@@ -0,0 +1,27 @@
+import 'dart:io';
+
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  test('Impeller opt-out is application-scoped', () {
+    final manifest = File('android/app/src/main/AndroidManifest.xml')
+        .readAsStringSync();
+
+    final applicationStart = manifest.indexOf('<application');
+    final applicationEnd = manifest.indexOf('</application>');
+    final activityStart = manifest.indexOf('<activity');
+    final activityEnd = manifest.indexOf('</activity>');
+    final impeller = manifest.indexOf(
+      'io.flutter.embedding.android.EnableImpeller',
+    );
+
+    expect(applicationStart, greaterThanOrEqualTo(0));
+    expect(applicationEnd, greaterThan(applicationStart));
+    expect(activityStart, greaterThan(applicationStart));
+    expect(activityEnd, greaterThan(activityStart));
+    expect(impeller, greaterThan(applicationStart));
+    expect(impeller, lessThan(applicationEnd));
+    // Flutter ignores this key under <activity>; keep it out of that block.
+    expect(impeller < activityStart || impeller > activityEnd, isTrue);
+  });
+}