App 2.19.6: remove the CarPlay full-screen switch and the export button

CarPlay's 1920x640 is fixed in Allgo's system app. On start the car
build removes the CarPlay entry from policy_control if the switch was
left on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
2026-10-23 22:02:15 +02:00
co-authored by Claude Opus 5.5
parent 4c224dcb3b
commit e6f8258203
13 changed files with 37 additions and 382 deletions
+8 -4
View File
@@ -35,9 +35,9 @@ Repository: `ssh://git@git.stinnesbeck.com:2222/nils/mg4.git` (remote `origin`),
`secrets.example.yaml` holds `CHANGE-ME` placeholders only. Stage files explicitly, check the `secrets.example.yaml` holds `CHANGE-ME` placeholders only. Stage files explicitly, check the
index for changes that are not yours (the owner works in parallel), and scan the staged diff index for changes that are not yours (the owner works in parallel), and scan the staged diff
for secrets before every commit. Never print a token. for secrets before every commit. Never print a token.
3. **Telemetry is read-only.** The app never writes a vehicle setting. The one system setting it 3. **Telemetry is read-only.** The app never writes a vehicle setting. The only system setting it
writes is `policy_control` (CarPlay full screen), only when the driver switches it on, and only touches is `policy_control`, and only to remove the CarPlay entry the withdrawn full-screen
its own entry. experiment (2.19.2–2.19.5) may have left.
4. **A failed read is not a zero.** Unreadable values are `null`, omitted from payloads and 4. **A failed read is not a zero.** Unreadable values are `null`, omitted from payloads and
shown as "—". Sending 0 for SOC tells ABRP the battery is empty. shown as "—". Sending 0 for SOC tells ABRP the battery is empty.
5. **Fail closed.** Updates (origin, SHA-256, package, signature), credentials, unknown state. 5. **Fail closed.** Updates (origin, SHA-256, package, signature), credentials, unknown state.
@@ -59,6 +59,10 @@ Repository: `ssh://git@git.stinnesbeck.com:2222/nils/mg4.git` (remote `origin`),
paint colour (only `ro.build.mtk2712.vehicle_type = AS28`). See `SPECS.md`, `FIRMWARE.md`. paint colour (only `ro.build.mtk2712.vehicle_type = AS28`). See `SPECS.md`, `FIRMWARE.md`.
- No third-party apps in the MG launcher: the app opens by a double press of the voice key. - No third-party apps in the MG launcher: the app opens by a double press of the voice key.
- Its internet comes from the car itself (no Wi-Fi needed). - Its internet comes from the car itself (no Wi-Fi needed).
- CarPlay is Allgo's `com.allgo.carplay.service` (`/system/app/AllgoCarplay_EH32`). Its picture
is hard-coded at 1920×640, 80 px below the top (constants in its code and layout), so it cannot
be made full screen from an app. Installs from an app's own session are refused ("Not allowed
to install non-system apps on internal storage"): updates end on the installer screen.
## The app ## The app
@@ -68,7 +72,7 @@ Repository: `ssh://git@git.stinnesbeck.com:2222/nils/mg4.git` (remote `origin`),
| Consumption / Charging | period totals, daily log, charging sessions and curve | | Consumption / Charging | period totals, daily log, charging sessions and curve |
| Live data | on/off, ABRP API key and user token, upload frequency | | Live data | on/off, ABRP API key and user token, upload frequency |
| Server | URL, pairing by QR, ingest token, installation ID (read-only), config import | | Server | URL, pairing by QR, ingest token, installation ID (read-only), config import |
| Settings | full screen, voice key, CarPlay full screen (experiment), tyre unit, accent colour, battery, charging price, version, update check, install from USB, About | | Settings | full screen, voice key, tyre unit, accent colour, battery, charging price, version, update check, install from USB, About |
| Logs | upload log | | Logs | upload log |
Key classes (`app/src/main/java/com/evsuite/abrp/`): `AbrpUploadService` (foreground service, Key classes (`app/src/main/java/com/evsuite/abrp/`): `AbrpUploadService` (foreground service,
+1 -1
View File
@@ -84,7 +84,7 @@ with the server using a token of its own, obtained by pairing with a phone (belo
| **Charging** | The current session (SOC, voltage, current, power, energy into the battery and from the grid, time, cost) and its SOC/power curve | | **Charging** | The current session (SOC, voltage, current, power, energy into the battery and from the grid, time, cost) and its SOC/power curve |
| **Live data** | Live data on/off, ABRP credentials for sending directly from the car, upload frequency | | **Live data** | Live data on/off, ABRP credentials for sending directly from the car, upload frequency |
| **Server** | Server URL, pairing by QR code, ingest token, installation ID, config import | | **Server** | Server URL, pairing by QR code, ingest token, installation ID, config import |
| **Settings** | Full screen, voice key, CarPlay full screen (experiment), tyre pressure unit, accent colour, battery (manual SoH, usable capacity), charging price, entry on the phone, version, update check, install from USB stick, About | | **Settings** | Full screen, voice key, tyre pressure unit, accent colour, battery (manual SoH, usable capacity), charging price, entry on the phone, version, update check, install from USB stick, About |
| **Logs** | Upload log | | **Logs** | Upload log |
- **Opening the app:** the MG4 launcher does not list third-party apps. **Double-press the - **Opening the app:** the MG4 launcher does not list third-party apps. **Double-press the
+5
View File
@@ -118,6 +118,11 @@ tyre pressures, GPS (lat, lon, elevation, heading) and the is_* flags. It does *
confirmed car facts, app structure, UI conventions, build/test/ship, documents. It replaces confirmed car facts, app structure, UI conventions, build/test/ship, documents. It replaces
upstream's EVABRPUploader text. The test emulator is now in the flake: `nix run .#car-emulator` upstream's EVABRPUploader text. The test emulator is now in the flake: `nix run .#car-emulator`
(Android 9 AOSP at 1920×720, 160 dpi; the car build installs as the system user — checked). (Android 9 AOSP at 1920×720, 160 dpi; the car build installs as the system user — checked).
- 2026-10-23 — App 2.19.6: the CarPlay full-screen switch and the "Export CarPlay data" button are
removed (CarPlay's size is fixed, see below). On start, the car build takes the CarPlay entry out
of `policy_control` if the switch was left on, and leaves anything else there alone — checked on
the emulator. The Foreground apps card stays as a diagnostic. AGENTS.md lists the CarPlay and
install findings among the confirmed car facts.
- 2026-10-23 — CarPlay full screen: not possible on this firmware (SWI68). The export from the - 2026-10-23 — CarPlay full screen: not possible on this firmware (SWI68). The export from the
car showed CarPlay's window at 1920×720 with both SAIC bars hidden (policy_control on), and its car showed CarPlay's window at 1920×720 with both SAIC bars hidden (policy_control on), and its
video SurfaceView at (0,80), 1920×640, with a 1920×640 buffer: the grey strip is the window's own video SurfaceView at (0,80), 1920×640, with a 1920×640 buffer: the grey strip is the window's own
+2 -2
View File
@@ -34,8 +34,8 @@ android {
applicationId "com.stinnesbeck.nilsmg4" applicationId "com.stinnesbeck.nilsmg4"
minSdk 27 minSdk 27
targetSdk 36 targetSdk 36
versionCode 104 versionCode 105
versionName "2.19.5" versionName "2.19.6"
// Nothing about the server ships in the APK: its URL and ingest token are entered in // Nothing about the server ships in the APK: its URL and ingest token are entered in
// the app's Server tab (ServerConfig), and the database is only reachable by the server. // the app's Server tab (ServerConfig), and the database is only reachable by the server.
@@ -1,234 +0,0 @@
package com.evsuite.abrp;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
/**
* Read-only diagnostic for the CarPlay resolution question: copies what decides CarPlay's size
* to a folder on the USB stick, for reading on a computer. It reads the car and writes only to
* the stick.
*
* What it writes to {@code <stick>/Android/data/<package>/files/nilsmg4-carplay-<time>/}:
* <ul>
* <li>the CarPlay app's APK files and its own native libraries;</li>
* <li>libraries and configuration files under /system, /vendor, /odm and /product whose path
* names CarPlay (see {@link #isCarPlayPath});</li>
* <li>the display-related system properties, never identifiers ({@link #keepProperty});</li>
* <li>the window manager, SurfaceFlinger and display state, captured while CarPlay is in front.</li>
* </ul>
* Only the system user (the car build) may run dumpsys; elsewhere those files say why not.
*/
final class CarPlayExport {
private static final String TAG = "EVABRP.CarPlayExport";
static final String CARPLAY_PACKAGE = "com.allgo.carplay.service";
private static final long MAX_FILE_BYTES = 64L << 20;
private static final int MAX_FOUND_FILES = 200;
private static final String[] SEARCH_ROOTS = {
"/system/etc", "/system/lib", "/system/lib64", "/system/priv-app", "/system/app",
"/vendor/etc", "/vendor/lib", "/vendor/lib64", "/vendor/app", "/vendor/priv-app",
"/odm/etc", "/product/etc", "/product/app", "/product/priv-app",
};
private static final Pattern CARPLAY_PATH = Pattern.compile("(?i)carplay|allgo|iap2|airplay");
private static final Pattern PROPERTY_KEEP = Pattern.compile(
"(?i)carplay|allgo|iap|airplay|display|lcd|screen|resolution|density|surface|hwui|"
+ "\\bsf\\.|\\.sf\\.|width|height|ro\\.build\\.(version|display|fingerprint|id)|"
+ "vehicle_type|projection");
private static final Pattern PROPERTY_DROP = Pattern.compile(
"(?i)serial|vin\\b|\\.vin|imei|iccid|imsi|mac|bt\\.addr|bdaddr|wifi|ssid|password|token|"
+ "key|account|phone|msisdn|sim");
private CarPlayExport() {}
/** A path whose name says it belongs to CarPlay. */
static boolean isCarPlayPath(String path) {
return path != null && CARPLAY_PATH.matcher(path).find();
}
/** A {@code [key]: [value]} getprop line worth exporting: display-related, never an identifier. */
static boolean keepProperty(String getpropLine) {
if (getpropLine == null) return false;
int end = getpropLine.indexOf("]:");
String key = end > 0 ? getpropLine.substring(0, end) : getpropLine;
return PROPERTY_KEEP.matcher(key).find() && !PROPERTY_DROP.matcher(key).find();
}
/** Runs the export; returns one line for the screen. Blocking: call it off the main thread. */
static String run(Context context) {
File stick = stickFolder(context);
if (stick == null) {
Log.w(TAG, "no USB stick");
return "No USB stick found";
}
String stamp = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US).format(new Date());
File out = new File(stick, "nilsmg4-carplay-" + stamp);
if (!out.mkdirs()) {
Log.w(TAG, "cannot create " + out);
return "Could not create " + out;
}
List<String> notes = new ArrayList<>();
int copied = 0;
// The live state first, while CarPlay is still in front.
command(out, "dumpsys-window.txt", notes, "dumpsys", "window", "windows");
command(out, "dumpsys-surfaceflinger.txt", notes, "dumpsys", "SurfaceFlinger");
command(out, "dumpsys-display.txt", notes, "dumpsys", "display");
command(out, "dumpsys-activity.txt", notes, "dumpsys", "activity", "activities");
command(out, "wm.txt", notes, "sh", "-c", "wm size; wm density");
properties(out, notes);
try {
ApplicationInfo app = context.getPackageManager().getApplicationInfo(CARPLAY_PACKAGE, 0);
notes.add("uid " + app.uid + ", flags 0x" + Integer.toHexString(app.flags)
+ ", source " + app.sourceDir + ", native " + app.nativeLibraryDir);
File apkDir = new File(out, "apk");
copied += copy(new File(app.sourceDir), apkDir, notes);
if (app.splitSourceDirs != null) {
for (String split : app.splitSourceDirs) copied += copy(new File(split), apkDir, notes);
}
File[] libs = app.nativeLibraryDir == null ? null : new File(app.nativeLibraryDir).listFiles();
if (libs != null) {
for (File lib : libs) copied += copy(lib, new File(out, "native"), notes);
}
} catch (Exception e) {
notes.add("CarPlay app not found: " + e);
}
List<File> found = new ArrayList<>();
for (String root : SEARCH_ROOTS) find(new File(root), 0, found);
for (File f : found) {
copied += copy(f, new File(out, "found" + f.getParent()), notes);
}
notes.add(found.size() + " CarPlay-named files under the system partitions");
write(new File(out, "notes.txt"), String.join("\n", notes) + "\n");
String result = "Exported " + copied + " files to " + out;
Log.i(TAG, result);
return result;
}
/**
* This app's own folder on the removable volume, or on any volume (the emulator) when there is
* none: <stick>/Android/data/<package>/files. Android 9 lets an app write there and nowhere
* else on a removable volume — the stick's top folder is read-only to it.
*/
private static File stickFolder(Context context) {
File fallback = null;
for (File dir : context.getExternalFilesDirs(null)) {
if (dir == null || UsbVolume.root(dir) == null) continue;
boolean removable;
try {
removable = Environment.isExternalStorageRemovable(dir);
} catch (IllegalArgumentException e) {
removable = false;
}
if (removable) return dir;
if (fallback == null) fallback = dir;
}
return fallback;
}
private static void find(File dir, int depth, List<File> found) {
if (depth > 5 || found.size() >= MAX_FOUND_FILES) return;
File[] entries = dir.listFiles();
if (entries == null) return;
for (File f : entries) {
if (found.size() >= MAX_FOUND_FILES) return;
if (f.isDirectory()) {
if (isCarPlayPath(f.getName())) {
File[] inner = f.listFiles();
if (inner != null) for (File g : inner) if (g.isFile()) found.add(g);
}
find(f, depth + 1, found);
} else if (isCarPlayPath(f.getName())) {
found.add(f);
}
}
}
private static void properties(File out, List<String> notes) {
String all = exec(notes, "getprop");
if (all == null) return;
StringBuilder kept = new StringBuilder();
for (String line : all.split("\n")) if (keepProperty(line)) kept.append(line).append('\n');
write(new File(out, "properties.txt"), kept.toString());
}
private static void command(File out, String name, List<String> notes, String... argv) {
String text = exec(notes, argv);
if (text != null) write(new File(out, name), text);
}
private static String exec(List<String> notes, String... argv) {
Process p = null;
try {
p = new ProcessBuilder(argv).redirectErrorStream(true).start();
p.getOutputStream().close();
byte[] bytes = readAll(p.getInputStream(), 8 << 20);
if (!p.waitFor(20, TimeUnit.SECONDS)) notes.add(String.join(" ", argv) + ": timed out");
return new String(bytes, StandardCharsets.UTF_8);
} catch (Exception e) {
notes.add(String.join(" ", argv) + ": " + e);
return null;
} finally {
if (p != null) p.destroy();
}
}
private static int copy(File source, File dir, List<String> notes) {
if (!source.isFile()) return 0;
if (source.length() > MAX_FILE_BYTES) {
notes.add("skipped (too large): " + source + " " + source.length());
return 0;
}
if (!dir.isDirectory() && !dir.mkdirs()) {
notes.add("no folder for " + source);
return 0;
}
try (InputStream in = new FileInputStream(source);
OutputStream os = new FileOutputStream(new File(dir, source.getName()))) {
byte[] buffer = new byte[64 * 1024];
int n;
while ((n = in.read(buffer)) != -1) os.write(buffer, 0, n);
return 1;
} catch (IOException e) {
notes.add("not copied: " + source + ": " + e.getMessage());
return 0;
}
}
private static byte[] readAll(InputStream in, int max) throws IOException {
java.io.ByteArrayOutputStream buffer = new java.io.ByteArrayOutputStream();
byte[] chunk = new byte[16 * 1024];
int n;
while ((n = in.read(chunk)) != -1 && buffer.size() < max) buffer.write(chunk, 0, n);
return buffer.toByteArray();
}
private static void write(File file, String text) {
try (OutputStream os = new FileOutputStream(file)) {
os.write(text.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
Log.w(TAG, "not written: " + file, e);
}
}
}
@@ -242,46 +242,9 @@ public class MainActivity extends AppCompatActivity {
if (r.statusBarPx != null) { if (r.statusBarPx != null) {
sb.append('\n').append(getString(R.string.foreground_status_bar, r.statusBarPx)); sb.append('\n').append(getString(R.string.foreground_status_bar, r.statusBarPx));
} }
if (lastCarPlayExport != null) {
sb.append('\n').append(getString(R.string.carplay_export_last, lastCarPlayExport));
}
return sb.toString(); return sb.toString();
} }
/** The last CarPlay export's outcome, for the Foreground apps card; null before the first. */
private static volatile String lastCarPlayExport;
/**
* Waits 20 seconds, so the driver can bring CarPlay to the front, then exports what decides
* its size to the USB stick (CarPlayExport). Runs in the app's process, which the upload
* service keeps alive while this activity is in the background.
*/
private void startCarPlayExport() {
android.content.Context app = getApplicationContext();
android.os.Handler main = new android.os.Handler(android.os.Looper.getMainLooper());
Toast.makeText(app, R.string.carplay_export_started, Toast.LENGTH_LONG).show();
new Thread(() -> {
try {
Thread.sleep(20_000);
} catch (InterruptedException e) {
return;
}
main.post(() -> Toast.makeText(app, R.string.carplay_export_running, Toast.LENGTH_SHORT).show());
String result;
try {
result = CarPlayExport.run(app);
} catch (Throwable t) {
result = "Export failed: " + t;
}
lastCarPlayExport = result;
final String shown = result;
main.post(() -> {
Toast.makeText(app, shown, Toast.LENGTH_LONG).show();
refreshForeground();
});
}, "carplay-export").start();
}
/** Refreshes state + call log while the screen is visible. */ /** Refreshes state + call log while the screen is visible. */
private final android.os.Handler uiHandler = new android.os.Handler(android.os.Looper.getMainLooper()); private final android.os.Handler uiHandler = new android.os.Handler(android.os.Looper.getMainLooper());
private final Runnable uiRefresh = new Runnable() { private final Runnable uiRefresh = new Runnable() {
@@ -335,7 +298,6 @@ public class MainActivity extends AppCompatActivity {
logPane.findViewById(R.id.flush_now_button).setOnClickListener(v -> manualFlush()); logPane.findViewById(R.id.flush_now_button).setOnClickListener(v -> manualFlush());
foregroundText = logPane.findViewById(R.id.foreground_text); foregroundText = logPane.findViewById(R.id.foreground_text);
logPane.findViewById(R.id.foreground_refresh_button).setOnClickListener(v -> refreshForeground()); logPane.findViewById(R.id.foreground_refresh_button).setOnClickListener(v -> refreshForeground());
logPane.findViewById(R.id.carplay_export_button).setOnClickListener(v -> startCarPlayExport());
setUpPager(); setUpPager();
// Back on the page that was open when the activity was recreated (accent colour change). // Back on the page that was open when the activity was recreated (accent colour change).
@@ -1105,54 +1067,27 @@ public class MainActivity extends AppCompatActivity {
// ---------- Upload page ---------- // ---------- Upload page ----------
/** The car's CarPlay app, as the Logs tab's foreground list showed it on SWI68. */
private static final String CARPLAY_PACKAGE = "com.allgo.carplay.service";
/** /**
* Experiment: forces CarPlay into immersive mode through Android 9's policy_control setting. * App 2.19.2–2.19.5 had a "Full screen for CarPlay" experiment that added CarPlay to Android 9's
* The switch shows what the setting holds, not a preference, and switching off removes only * policy_control setting; CarPlay's 1920×640 picture turned out to be fixed in its own app, so
* our entry. Writing it needs WRITE_SECURE_SETTINGS, which only the car build (system user) * the switch is gone. This takes our entry out again where the switch was left on, and leaves
* has; elsewhere the switch is disabled. * anything else in the setting alone. Only the car build (system user) may write it.
*/ */
private void bindCarPlayFullScreen() { private void clearCarPlayFullScreen() {
com.google.android.material.switchmaterial.SwitchMaterial sw = if (checkSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
settingsPane.findViewById(R.id.carplay_full_screen_switch); != android.content.pm.PackageManager.PERMISSION_GRANTED) return;
boolean mayWrite = checkSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) diagReader.execute(() -> {
== android.content.pm.PackageManager.PERMISSION_GRANTED;
String current = android.provider.Settings.Global.getString(getContentResolver(), PolicyControl.KEY);
sw.setOnCheckedChangeListener(null);
sw.setChecked(PolicyControl.hasFull(current, CARPLAY_PACKAGE));
sw.setEnabled(mayWrite);
if (!mayWrite) {
((TextView) settingsPane.findViewById(R.id.carplay_full_screen_hint))
.setText(R.string.hint_carplay_full_screen_unavailable);
return;
}
sw.setOnCheckedChangeListener((btn, checked) -> diagReader.execute(() -> {
boolean ok;
try { try {
android.content.ContentResolver cr = getContentResolver(); android.content.ContentResolver cr = getContentResolver();
String before = android.provider.Settings.Global.getString(cr, PolicyControl.KEY); String before = android.provider.Settings.Global.getString(cr, PolicyControl.KEY);
String after = checked ? PolicyControl.withFull(before, CARPLAY_PACKAGE) if (!PolicyControl.hasFull(before, PolicyControl.CARPLAY_PACKAGE)) return;
: PolicyControl.withoutFull(before, CARPLAY_PACKAGE); android.provider.Settings.Global.putString(cr, PolicyControl.KEY,
android.provider.Settings.Global.putString(cr, PolicyControl.KEY, after); PolicyControl.withoutFull(before, PolicyControl.CARPLAY_PACKAGE));
String now = android.provider.Settings.Global.getString(cr, PolicyControl.KEY); android.util.Log.i("EVABRP.Settings", "CarPlay entry removed from policy_control");
ok = PolicyControl.hasFull(now, CARPLAY_PACKAGE) == checked;
android.util.Log.i("EVABRP.Settings", "policy_control " + (checked ? "on" : "off") + ": " + now);
} catch (RuntimeException e) { } catch (RuntimeException e) {
android.util.Log.w("EVABRP.Settings", "policy_control not written", e); android.util.Log.w("EVABRP.Settings", "policy_control not cleared", e);
ok = false;
} }
final boolean written = ok; });
runOnUiThread(() -> {
if (isDestroyed()) return;
if (!written) {
Toast.makeText(this, R.string.carplay_full_screen_failed, Toast.LENGTH_LONG).show();
bindCarPlayFullScreen();
}
refreshForeground();
});
}));
} }
private void bindServicePane() { private void bindServicePane() {
@@ -1168,7 +1103,7 @@ public class MainActivity extends AppCompatActivity {
prefs.edit().putBoolean(UploadSettings.KEY_FULL_SCREEN, checked).apply(); prefs.edit().putBoolean(UploadSettings.KEY_FULL_SCREEN, checked).apply();
applyFullScreen(); applyFullScreen();
}); });
bindCarPlayFullScreen(); clearCarPlayFullScreen();
liveTelemetrySwitch = servicePane.findViewById(R.id.abrp_upload_switch); liveTelemetrySwitch = servicePane.findViewById(R.id.abrp_upload_switch);
intervalChoices = servicePane.findViewById(R.id.interval_choices); intervalChoices = servicePane.findViewById(R.id.interval_choices);
boostSwitch = servicePane.findViewById(R.id.boost_switch); boostSwitch = servicePane.findViewById(R.id.boost_switch);
@@ -7,14 +7,16 @@ import java.util.Map;
/** /**
* The value of Android 9's {@code policy_control} global setting, which forces other apps into * The value of Android 9's {@code policy_control} global setting, which forces other apps into
* immersive mode: {@code immersive.full=pkg1,pkg2:immersive.status=pkg3}. This adds or removes * immersive mode: {@code immersive.full=pkg1,pkg2:immersive.status=pkg3}. Adds or removes one
* one package in {@code immersive.full} and leaves every other entry as it was, so switching * package in {@code immersive.full} and leaves every other entry as it was. Kept to clear the
* our experiment off restores what was there before. Pure Java, so it is unit-tested. * entry the removed CarPlay experiment wrote. Pure Java, so it is unit-tested.
*/ */
final class PolicyControl { final class PolicyControl {
static final String KEY = "policy_control"; static final String KEY = "policy_control";
static final String FULL = "immersive.full"; static final String FULL = "immersive.full";
/** The car's CarPlay app, which the removed full-screen experiment added. */
static final String CARPLAY_PACKAGE = "com.allgo.carplay.service";
private PolicyControl() {} private PolicyControl() {}
@@ -10,7 +10,6 @@
<LinearLayout android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="24dp" android:paddingEnd="24dp" android:paddingTop="16dp" android:paddingBottom="16dp" android:background="@drawable/bg_glass"> <LinearLayout android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="24dp" android:paddingEnd="24dp" android:paddingTop="16dp" android:paddingBottom="16dp" android:background="@drawable/bg_glass">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical">
<TextView android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:text="@string/foreground_title" android:textAppearance="@style/Text.EV.Eyebrow" /> <TextView android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:text="@string/foreground_title" android:textAppearance="@style/Text.EV.Eyebrow" />
<com.google.android.material.button.MaterialButton android:id="@+id/carplay_export_button" style="@style/Widget.EV.Button.Outlined" android:layout_width="wrap_content" android:layout_height="48dp" android:layout_marginEnd="8dp" android:minWidth="0dp" android:paddingStart="16dp" android:paddingEnd="16dp" android:text="@string/btn_carplay_export" />
<com.google.android.material.button.MaterialButton android:id="@+id/foreground_refresh_button" style="@style/Widget.EV.Button.Outlined" android:layout_width="48dp" android:layout_height="48dp" android:minWidth="0dp" android:padding="0dp" android:contentDescription="@string/btn_refresh" app:icon="@drawable/ic_refresh" app:iconGravity="textStart" app:iconPadding="0dp" app:iconTint="?attr/evAccent" /> <com.google.android.material.button.MaterialButton android:id="@+id/foreground_refresh_button" style="@style/Widget.EV.Button.Outlined" android:layout_width="48dp" android:layout_height="48dp" android:minWidth="0dp" android:padding="0dp" android:contentDescription="@string/btn_refresh" app:icon="@drawable/ic_refresh" app:iconGravity="textStart" app:iconPadding="0dp" app:iconTint="?attr/evAccent" />
</LinearLayout> </LinearLayout>
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:text="@string/foreground_hint" android:textAppearance="@style/Text.EV.Secondary" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:text="@string/foreground_hint" android:textAppearance="@style/Text.EV.Secondary" />
@@ -10,8 +10,6 @@
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_full_screen" android:textAppearance="@style/Text.EV.Secondary" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_full_screen" android:textAppearance="@style/Text.EV.Secondary" />
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/voice_key_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_voice_key" android:textAppearance="@style/Text.EV.Body" android:paddingTop="8dp" android:paddingBottom="8dp" app:useMaterialThemeColors="true" app:thumbTint="@color/switch_thumb" app:trackTint="@color/switch_track" android:textColor="@color/ev_text_primary" /> <com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/voice_key_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_voice_key" android:textAppearance="@style/Text.EV.Body" android:paddingTop="8dp" android:paddingBottom="8dp" app:useMaterialThemeColors="true" app:thumbTint="@color/switch_thumb" app:trackTint="@color/switch_track" android:textColor="@color/ev_text_primary" />
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_voice_key" android:textAppearance="@style/Text.EV.Secondary" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_voice_key" android:textAppearance="@style/Text.EV.Secondary" />
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/carplay_full_screen_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_carplay_full_screen" android:textAppearance="@style/Text.EV.Body" android:paddingTop="8dp" android:paddingBottom="8dp" app:useMaterialThemeColors="true" app:thumbTint="@color/switch_thumb" app:trackTint="@color/switch_track" android:textColor="@color/ev_text_primary" />
<TextView android:id="@+id/carplay_full_screen_hint" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_carplay_full_screen" android:textAppearance="@style/Text.EV.Secondary" />
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:baselineAligned="false"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:baselineAligned="false">
<LinearLayout android:layout_width="0dp" android:layout_weight="2" android:layout_height="wrap_content" android:layout_marginEnd="12dp" android:orientation="vertical"> <LinearLayout android:layout_width="0dp" android:layout_weight="2" android:layout_height="wrap_content" android:layout_marginEnd="12dp" android:orientation="vertical">
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/label_tire_unit" android:textAppearance="@style/Text.EV.Body" android:layout_marginTop="8dp" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/label_tire_unit" android:textAppearance="@style/Text.EV.Body" android:layout_marginTop="8dp" />
-1
View File
@@ -9,7 +9,6 @@
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="24dp" android:background="@drawable/bg_glass" android:layout_marginTop="16dp"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="24dp" android:background="@drawable/bg_glass" android:layout_marginTop="16dp">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical">
<TextView android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:text="@string/foreground_title" android:textAppearance="@style/Text.EV.Eyebrow" /> <TextView android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:text="@string/foreground_title" android:textAppearance="@style/Text.EV.Eyebrow" />
<com.google.android.material.button.MaterialButton android:id="@+id/carplay_export_button" style="@style/Widget.EV.Button.Outlined" android:layout_width="wrap_content" android:layout_height="48dp" android:layout_marginEnd="8dp" android:minWidth="0dp" android:paddingStart="16dp" android:paddingEnd="16dp" android:text="@string/btn_carplay_export" />
<com.google.android.material.button.MaterialButton android:id="@+id/foreground_refresh_button" style="@style/Widget.EV.Button.Outlined" android:layout_width="48dp" android:layout_height="48dp" android:minWidth="0dp" android:padding="0dp" android:contentDescription="@string/btn_refresh" app:icon="@drawable/ic_refresh" app:iconGravity="textStart" app:iconPadding="0dp" app:iconTint="?attr/evAccent" /> <com.google.android.material.button.MaterialButton android:id="@+id/foreground_refresh_button" style="@style/Widget.EV.Button.Outlined" android:layout_width="48dp" android:layout_height="48dp" android:minWidth="0dp" android:padding="0dp" android:contentDescription="@string/btn_refresh" app:icon="@drawable/ic_refresh" app:iconGravity="textStart" app:iconPadding="0dp" app:iconTint="?attr/evAccent" />
</LinearLayout> </LinearLayout>
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:text="@string/foreground_hint" android:textAppearance="@style/Text.EV.Secondary" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:text="@string/foreground_hint" android:textAppearance="@style/Text.EV.Secondary" />
@@ -7,8 +7,6 @@
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_full_screen" android:textAppearance="@style/Text.EV.Secondary" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_full_screen" android:textAppearance="@style/Text.EV.Secondary" />
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/voice_key_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_voice_key" android:textAppearance="@style/Text.EV.Body" android:paddingTop="8dp" android:paddingBottom="8dp" app:useMaterialThemeColors="true" app:thumbTint="@color/switch_thumb" app:trackTint="@color/switch_track" android:textColor="@color/ev_text_primary" /> <com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/voice_key_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_voice_key" android:textAppearance="@style/Text.EV.Body" android:paddingTop="8dp" android:paddingBottom="8dp" app:useMaterialThemeColors="true" app:thumbTint="@color/switch_thumb" app:trackTint="@color/switch_track" android:textColor="@color/ev_text_primary" />
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_voice_key" android:textAppearance="@style/Text.EV.Secondary" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_voice_key" android:textAppearance="@style/Text.EV.Secondary" />
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/carplay_full_screen_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_carplay_full_screen" android:textAppearance="@style/Text.EV.Body" android:paddingTop="8dp" android:paddingBottom="8dp" app:useMaterialThemeColors="true" app:thumbTint="@color/switch_thumb" app:trackTint="@color/switch_track" android:textColor="@color/ev_text_primary" />
<TextView android:id="@+id/carplay_full_screen_hint" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_carplay_full_screen" android:textAppearance="@style/Text.EV.Secondary" />
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/label_tire_unit" android:textAppearance="@style/Text.EV.Body" android:layout_marginTop="20dp" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/label_tire_unit" android:textAppearance="@style/Text.EV.Body" android:layout_marginTop="20dp" />
<com.google.android.material.textfield.TextInputLayout style="@style/Widget.EV.Dropdown" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp"> <com.google.android.material.textfield.TextInputLayout style="@style/Widget.EV.Dropdown" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp">
<com.google.android.material.textfield.MaterialAutoCompleteTextView android:id="@+id/tire_unit_choices" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:inputType="none" android:textSize="20sp" app:simpleItemLayout="@layout/dropdown_item" /> <com.google.android.material.textfield.MaterialAutoCompleteTextView android:id="@+id/tire_unit_choices" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:inputType="none" android:textSize="20sp" app:simpleItemLayout="@layout/dropdown_item" />
+1 -9
View File
@@ -114,10 +114,6 @@
<string name="label_full_screen">Full screen</string> <string name="label_full_screen">Full screen</string>
<string name="hint_full_screen">Hides the car\'s top and bottom bars while this app is open. Swipe in from the top or bottom edge to show them briefly, e.g. for the climate controls.</string> <string name="hint_full_screen">Hides the car\'s top and bottom bars while this app is open. Swipe in from the top or bottom edge to show them briefly, e.g. for the climate controls.</string>
<string name="label_carplay_full_screen">Full screen for CarPlay (experiment)</string>
<string name="hint_carplay_full_screen">Hides the car\'s bars over CarPlay too, from its next start. A black strip? Reconnect the phone. Off restores the setting.</string>
<string name="hint_carplay_full_screen_unavailable">Needs the car build: only a system app may change this.</string>
<string name="carplay_full_screen_failed">Could not change the CarPlay full-screen setting</string>
<string name="label_voice_key">Double-press the voice key to open this app</string> <string name="label_voice_key">Double-press the voice key to open this app</string>
<string name="hint_voice_key">Press the voice key on the steering wheel twice quickly. The voice assistant may still react to the presses.</string> <string name="hint_voice_key">Press the voice key on the steering wheel twice quickly. The voice assistant may still react to the presses.</string>
<string name="label_abrp_upload_enabled">Send live data</string> <string name="label_abrp_upload_enabled">Send live data</string>
@@ -174,11 +170,7 @@
<string name="update_log_title">Updates, newest first:</string> <string name="update_log_title">Updates, newest first:</string>
<string name="btn_flush_now">Upload now</string> <string name="btn_flush_now">Upload now</string>
<string name="foreground_title">Foreground apps</string> <string name="foreground_title">Foreground apps</string>
<string name="btn_carplay_export">Export CarPlay data</string> <string name="foreground_hint">Which app was in front and when, from Android\'s own record. Reads only.</string>
<string name="carplay_export_started">Open CarPlay now: the export to the USB stick starts in 20 seconds</string>
<string name="carplay_export_running">Exporting CarPlay data to the USB stick…</string>
<string name="carplay_export_last">Last export: %1$s</string>
<string name="foreground_hint">For the CarPlay full-screen test: open CarPlay, come back here, read its name off the list. Reads only.</string>
<string name="foreground_recent">Last 30 minutes, newest first:</string> <string name="foreground_recent">Last 30 minutes, newest first:</string>
<string name="foreground_none">No app came to the front in the last 30 minutes.</string> <string name="foreground_none">No app came to the front in the last 30 minutes.</string>
<string name="foreground_unavailable">Not readable in this build: only the car build (system app) may see other apps.</string> <string name="foreground_unavailable">Not readable in this build: only the car build (system app) may see other apps.</string>
@@ -1,43 +0,0 @@
package com.evsuite.abrp;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
/** What the CarPlay export takes along — pure JVM. */
public class CarPlayExportTest {
@Test
public void displayPropertiesAreKept() {
assertTrue(CarPlayExport.keepProperty("[ro.sf.lcd_density]: [160]"));
assertTrue(CarPlayExport.keepProperty("[persist.allgo.carplay.height]: [640]"));
assertTrue(CarPlayExport.keepProperty("[ro.build.mtk2712.vehicle_type]: [AS28]"));
assertTrue(CarPlayExport.keepProperty("[ro.build.display.id]: [SWI68-29958-1300R69]"));
}
@Test
public void identifiersNeverLeave() {
assertFalse(CarPlayExport.keepProperty("[ro.serialno]: [ABC123]"));
assertFalse(CarPlayExport.keepProperty("[persist.vehicle.vin]: [LSJ...]"));
assertFalse(CarPlayExport.keepProperty("[persist.carplay.wifi.ssid]: [MG4-1234]"));
assertFalse(CarPlayExport.keepProperty("[persist.carplay.bt.addr]: [00:11:22:33:44:55]"));
assertFalse(CarPlayExport.keepProperty("[ro.boot.serialno]: [x]"));
assertFalse(CarPlayExport.keepProperty(null));
}
@Test
public void unrelatedPropertiesStay() {
assertFalse(CarPlayExport.keepProperty("[dalvik.vm.heapsize]: [512m]"));
assertFalse(CarPlayExport.keepProperty("[persist.sys.timezone]: [Europe/Berlin]"));
}
@Test
public void carPlayPathsAreRecognised() {
assertTrue(CarPlayExport.isCarPlayPath("/vendor/etc/allgo/config.xml"));
assertTrue(CarPlayExport.isCarPlayPath("libCarPlayCore.so"));
assertTrue(CarPlayExport.isCarPlayPath("iap2_config.ini"));
assertFalse(CarPlayExport.isCarPlayPath("/system/etc/hosts"));
assertFalse(CarPlayExport.isCarPlayPath(null));
}
}