App 2.17.1: tidier Settings and Logs
- Settings: the Wi-Fi and saved-networks cards are gone, and the rest sits in three columns (display and steering wheel | battery | charging price and app) instead of one tall column. The background Wi-Fi auto-connect stays on, without a switch. - Logs: only the upload log. The steering-key reports, the car-colour probe and the screen line answered their questions and are removed, with their code (CarColorProbe, the key-report log) and strings. - README lists the current tabs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
This commit is contained in:
@@ -1,59 +0,0 @@
|
||||
package com.evsuite.abrp;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Looks for the car's paint colour among the head unit's system properties, for the Logs tab.
|
||||
*
|
||||
* The MG launcher draws the car in its own colour, so the head unit knows it somewhere; where is
|
||||
* not documented. This lists every property whose name or value mentions a colour or paint, so
|
||||
* a photo of the Logs tab shows whether (and how) it can be read. Read-only: it runs the
|
||||
* platform's own getprop, which only reads.
|
||||
*/
|
||||
final class CarColorProbe {
|
||||
|
||||
private static final Pattern LINE = Pattern.compile("^\\[([^\\]]*)\\]: \\[(.*)\\]$");
|
||||
private static final Pattern INTERESTING = Pattern.compile(
|
||||
"(?i)colou?r|paint|body_?col|exterior|car_?model|vehicle_?(config|type|model)");
|
||||
|
||||
private CarColorProbe() { }
|
||||
|
||||
/** getprop's output, filtered; at most {@code max} lines. Never throws. */
|
||||
static List<String> run(int max) {
|
||||
try {
|
||||
Process p = new ProcessBuilder("getprop").redirectErrorStream(true).start();
|
||||
StringBuilder out = new StringBuilder();
|
||||
try (BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = r.readLine()) != null && out.length() < 512 * 1024) out.append(line).append('\n');
|
||||
}
|
||||
p.waitFor();
|
||||
return filter(out.toString(), max);
|
||||
} catch (Exception e) {
|
||||
List<String> failed = new ArrayList<>();
|
||||
failed.add("getprop failed: " + e.getClass().getSimpleName());
|
||||
return failed;
|
||||
}
|
||||
}
|
||||
|
||||
/** The "name = value" lines whose name or value looks colour- or model-related. Pure. */
|
||||
static List<String> filter(String getpropOutput, int max) {
|
||||
List<String> found = new ArrayList<>();
|
||||
for (String line : getpropOutput.split("\n")) {
|
||||
Matcher m = LINE.matcher(line.trim());
|
||||
if (!m.matches()) continue;
|
||||
String name = m.group(1), value = m.group(2);
|
||||
if (INTERESTING.matcher(name).find() || INTERESTING.matcher(value).find()) {
|
||||
found.add(name + " = " + value);
|
||||
if (found.size() >= max) break;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,11 @@ import com.evsuite.hardware.telemetry.EnergyTelemetryReader;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
/** Shared with {@link AbrpUploadService}, which reads it on every tick. */
|
||||
/**
|
||||
* Rejoin a saved network (the phone hotspot) in the background; read by
|
||||
* {@link AbrpUploadService} on every tick. On by default; the Wi-Fi card with its switch was
|
||||
* removed from Settings (2.17.1), so it now stays on.
|
||||
*/
|
||||
static final String WIFI_AUTO_KEY = "wifi_auto_connect";
|
||||
private static final String REPOSITORY_URL = "https://git.stinnesbeck.com/nils/mg4";
|
||||
|
||||
@@ -76,9 +80,6 @@ public class MainActivity extends AppCompatActivity {
|
||||
/** The pages in navigation order. Parallel to {@link #TAB_IDS}. */
|
||||
private View[] panes;
|
||||
|
||||
/** Wi-Fi page. The helper is the same one the upload service uses on its own tick. */
|
||||
private WifiAutoConnect wifi;
|
||||
private TextView wifiSsid, wifiState, wifiSavedList;
|
||||
|
||||
/**
|
||||
* Value cells of the Vehicle page, looked up once and refreshed on the UI tick.
|
||||
@@ -126,7 +127,6 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
/** Upload page: live telemetry switch and cadence. */
|
||||
private SwitchMaterial liveTelemetrySwitch, boostSwitch, voiceKeySwitch, fullScreenSwitch;
|
||||
private TextView steeringKeysText;
|
||||
private com.google.android.material.textfield.MaterialAutoCompleteTextView intervalChoices;
|
||||
private TextInputLayout lowSocLayout;
|
||||
private TextInputEditText lowSocInput;
|
||||
@@ -215,10 +215,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
@Override public void run() {
|
||||
refreshStatus();
|
||||
refreshCallLog();
|
||||
refreshSteeringKeys();
|
||||
sampleTrackers();
|
||||
refreshConsumption();
|
||||
refreshWifi();
|
||||
uiHandler.postDelayed(this, 2_000L);
|
||||
}
|
||||
};
|
||||
@@ -254,22 +252,12 @@ public class MainActivity extends AppCompatActivity {
|
||||
bindVehiclePane();
|
||||
bindConsumptionPane();
|
||||
bindChargingPane();
|
||||
bindWifiPane();
|
||||
bindServerPane();
|
||||
bindServicePane();
|
||||
requestLocationPermissionIfNeeded();
|
||||
|
||||
statusText = servicePane.findViewById(R.id.status_text);
|
||||
callLogText = logPane.findViewById(R.id.call_log_text);
|
||||
steeringKeysText = logPane.findViewById(R.id.steering_keys_text);
|
||||
TextView carColorText = logPane.findViewById(R.id.car_color_text);
|
||||
showScreenInfo(logPane.findViewById(R.id.screen_text));
|
||||
logPane.findViewById(R.id.car_color_button).setOnClickListener(v -> new Thread(() -> {
|
||||
java.util.List<String> found = CarColorProbe.run(60);
|
||||
String shown = found.isEmpty() ? getString(R.string.car_color_none)
|
||||
: android.text.TextUtils.join("\n", found);
|
||||
runOnUiThread(() -> carColorText.setText(shown));
|
||||
}, "car-color-probe").start());
|
||||
logPane.findViewById(R.id.flush_now_button).setOnClickListener(v -> manualFlush());
|
||||
|
||||
setUpPager();
|
||||
@@ -292,21 +280,6 @@ public class MainActivity extends AppCompatActivity {
|
||||
startForegroundService(new Intent(this, AbrpUploadService.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* What the head unit reports about its screen, for the Logs tab: the panel in pixels and its
|
||||
* density, and this app's window in pixels and in dp, which is what layouts are chosen by.
|
||||
* Sources disagree on the MG4's numbers (1920×720, 1280×480, a 1778×720 window).
|
||||
*/
|
||||
@SuppressWarnings("deprecation") // getRealMetrics: the WindowMetrics API starts at 30; this is 28
|
||||
private void showScreenInfo(TextView view) {
|
||||
android.util.DisplayMetrics panel = new android.util.DisplayMetrics();
|
||||
getWindowManager().getDefaultDisplay().getRealMetrics(panel);
|
||||
android.util.DisplayMetrics window = getResources().getDisplayMetrics();
|
||||
android.content.res.Configuration config = getResources().getConfiguration();
|
||||
view.setText(getString(R.string.screen_info, panel.widthPixels, panel.heightPixels, panel.densityDpi,
|
||||
window.widthPixels, window.heightPixels, config.screenWidthDp, config.screenHeightDp));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a dialog without bringing the car's bars back. A dialog is a window of its own, and
|
||||
* the system shows the bars when a window without the full-screen flags takes focus. So it
|
||||
@@ -1224,45 +1197,6 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Wi-Fi page ----------
|
||||
|
||||
private void bindWifiPane() {
|
||||
wifi = new WifiAutoConnect(getApplicationContext());
|
||||
wifiSsid = settingsPane.findViewById(R.id.wifi_ssid);
|
||||
wifiState = settingsPane.findViewById(R.id.wifi_state);
|
||||
wifiSavedList = settingsPane.findViewById(R.id.wifi_saved_list);
|
||||
|
||||
SwitchMaterial auto = settingsPane.findViewById(R.id.wifi_auto_switch);
|
||||
auto.setChecked(prefs.getBoolean(WIFI_AUTO_KEY, true));
|
||||
auto.setOnCheckedChangeListener((btn, checked) ->
|
||||
prefs.edit().putBoolean(WIFI_AUTO_KEY, checked).apply());
|
||||
|
||||
settingsPane.findViewById(R.id.wifi_connect_button).setOnClickListener(v -> {
|
||||
// force: the driver is asking now, so the rate limit does not apply.
|
||||
WifiAutoConnect.Result r = wifi.ensureConnected(true);
|
||||
wifiState.setText(r.message);
|
||||
refreshWifi();
|
||||
});
|
||||
}
|
||||
|
||||
private void refreshWifi() {
|
||||
if (wifi == null) return;
|
||||
String ssid = wifi.currentSsid();
|
||||
wifiSsid.setText(ssid == null ? getString(R.string.wifi_not_connected) : ssid);
|
||||
if (ssid != null) {
|
||||
wifiState.setText(R.string.wifi_connected);
|
||||
} else if (!wifi.isWifiEnabled()) {
|
||||
wifiState.setText(R.string.wifi_off);
|
||||
}
|
||||
// Left alone otherwise: a message the button just set says more about what is
|
||||
// happening than "not connected" would.
|
||||
|
||||
java.util.List<String> saved = wifi.savedNetworkNames();
|
||||
wifiSavedList.setText(saved.isEmpty()
|
||||
? getString(R.string.wifi_saved_empty)
|
||||
: android.text.TextUtils.join("\n", saved));
|
||||
}
|
||||
|
||||
// ---------- Vehicle page ----------
|
||||
|
||||
/** Looks up the value cells once; the labels are static and never touched from code. */
|
||||
@@ -1752,15 +1686,6 @@ public class MainActivity extends AppCompatActivity {
|
||||
}, "cloud-refresh").start();
|
||||
}
|
||||
|
||||
/** What the car's steering-wheel key broadcasts looked like, newest first. */
|
||||
private void refreshSteeringKeys() {
|
||||
if (steeringKeysText == null) return;
|
||||
java.util.List<String> keys = SteeringKeyListener.recent();
|
||||
String text = keys.isEmpty() ? getString(R.string.steering_keys_empty)
|
||||
: android.text.TextUtils.join("\n", keys);
|
||||
if (!text.contentEquals(steeringKeysText.getText())) steeringKeysText.setText(text);
|
||||
}
|
||||
|
||||
private void refreshCallLog() {
|
||||
java.util.List<UploadLog.Entry> entries = AbrpUploadService.log().recent();
|
||||
if (entries.isEmpty()) {
|
||||
|
||||
@@ -9,20 +9,12 @@ import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Opens the app on a double press of the steering wheel's voice key.
|
||||
*
|
||||
* The head unit reports steering-wheel keys as the broadcast {@link #ACTION}; code 287 is the
|
||||
* voice key (see EVHardware's PhysicalButtonEventDecoder). The extras' format was read off the
|
||||
* car (see {@link SteeringKeyReport}); every report is still kept raw for the Logs tab, in case
|
||||
* another firmware sends them differently.
|
||||
* car (see {@link SteeringKeyReport}).
|
||||
*
|
||||
* Registered by the always-on service on the main thread; never throws into the head unit.
|
||||
*/
|
||||
@@ -31,9 +23,6 @@ final class SteeringKeyListener {
|
||||
static final String ACTION = "com.saic.keyevent.hardkey.report";
|
||||
static final int VOICE_KEY = 287;
|
||||
private static final String TAG = "NilsMG4.SteeringKey";
|
||||
private static final int MAX_LOG = 20;
|
||||
|
||||
private static final ArrayDeque<String> RECENT = new ArrayDeque<>();
|
||||
|
||||
private final Context context;
|
||||
private final SharedPreferences prefs;
|
||||
@@ -79,18 +68,6 @@ final class SteeringKeyListener {
|
||||
registered = false;
|
||||
}
|
||||
|
||||
/** The last broadcasts, newest first, for the Logs tab. */
|
||||
static synchronized List<String> recent() {
|
||||
List<String> out = new ArrayList<>(RECENT);
|
||||
java.util.Collections.reverse(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
private static synchronized void remember(String line) {
|
||||
if (RECENT.size() >= MAX_LOG) RECENT.removeFirst();
|
||||
RECENT.addLast(line);
|
||||
}
|
||||
|
||||
private void handle(Intent intent) {
|
||||
Bundle extras = intent.getExtras();
|
||||
SteeringKeyReport key = read(extras);
|
||||
@@ -98,11 +75,6 @@ final class SteeringKeyListener {
|
||||
if (key.code != null && UploadSettings.voiceKeyOpensApp(prefs)) {
|
||||
opens = detector.accept(key.code, key.down, key.longPress, android.os.SystemClock.elapsedRealtime());
|
||||
}
|
||||
remember(new SimpleDateFormat("HH:mm:ss.SSS", Locale.US).format(new Date())
|
||||
+ " " + describe(extras)
|
||||
+ "\n -> code " + (key.code == null ? "?" : key.code)
|
||||
+ ", down " + (key.down == null ? "?" : key.down)
|
||||
+ ", long " + key.longPress + (opens ? " OPENS APP" : ""));
|
||||
if (opens) {
|
||||
try {
|
||||
context.startActivity(new Intent(context, MainActivity.class)
|
||||
@@ -129,17 +101,4 @@ final class SteeringKeyListener {
|
||||
}
|
||||
return SteeringKeyReport.parse(values);
|
||||
}
|
||||
|
||||
/** All extras as name=value(type), so the Logs tab shows the real format. */
|
||||
static String describe(Bundle extras) {
|
||||
if (extras == null || extras.isEmpty()) return "(no extras)";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String k : extras.keySet()) {
|
||||
Object v = extras.get(k);
|
||||
if (sb.length() > 0) sb.append(", ");
|
||||
sb.append(k).append('=').append(v).append(" (")
|
||||
.append(v == null ? "null" : v.getClass().getSimpleName()).append(')');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,34 +58,6 @@ final class WifiAutoConnect {
|
||||
}
|
||||
}
|
||||
|
||||
boolean isWifiEnabled() {
|
||||
try {
|
||||
return wifi != null && wifi.isWifiEnabled();
|
||||
} catch (Throwable t) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Saved network names, for the UI to show what it can reach. Empty if the OS refuses. */
|
||||
List<String> savedNetworkNames() {
|
||||
java.util.List<String> names = new java.util.ArrayList<>();
|
||||
try {
|
||||
if (wifi == null) return names;
|
||||
List<WifiConfiguration> saved = wifi.getConfiguredNetworks();
|
||||
if (saved == null) return names;
|
||||
for (WifiConfiguration c : saved) {
|
||||
if (c == null || c.SSID == null) continue;
|
||||
String name = c.SSID.replace("\"", "").trim();
|
||||
if (!name.isEmpty() && !names.contains(name)) names.add(name);
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
Log.w(TAG, "Saved networks unavailable: permission denied");
|
||||
} catch (Throwable t) {
|
||||
Log.w(TAG, "getConfiguredNetworks refused: " + t.getMessage());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* One attempt to get onto a saved network, rate-limited.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user