App 2.17.2: Wi-Fi auto-connect removed

Upstream kept the head unit on the driver's phone hotspot by re-joining
a saved network every few minutes. This car's head unit has its own
connection and the Wi-Fi is not used, so the feature goes entirely:
WifiAutoConnect, its call on the service tick, the preference key, and
the ACCESS_WIFI_STATE / CHANGE_WIFI_STATE permissions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
This commit is contained in:
2026-10-18 20:34:05 +02:00
co-authored by Claude Opus 5.5
parent 851a4a34d4
commit d4bb502026
7 changed files with 7 additions and 160 deletions
+2 -2
View File
@@ -34,8 +34,8 @@ android {
applicationId "com.stinnesbeck.nilsmg4"
minSdk 27
targetSdk 36
versionCode 90
versionName "2.17.1"
versionCode 91
versionName "2.17.2"
// 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.
-6
View File
@@ -14,12 +14,6 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<!-- Wi-Fi auto-join. CHANGE_WIFI_STATE covers enabling the radio and asking the
supplicant to join a network the unit already has saved; ACCESS_WIFI_STATE covers
reading which one it landed on. No password is ever handled here — see
WifiAutoConnect for why this works on this Android 9 unit and not on a newer one. -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<!-- Android Automotive / CarPropertyManager permissions -->
<uses-permission android:name="android.car.permission.CAR_SPEED" />
@@ -68,7 +68,6 @@ public class AbrpUploadService extends Service {
private ChargeSessionTracker chargeSessionTracker;
private TelemetryUploader telemetryUploader;
private SteeringKeyListener steeringKeys;
private WifiAutoConnect wifi;
private ScheduledExecutorService scheduler;
private SharedPreferences prefs;
@@ -97,7 +96,6 @@ public class AbrpUploadService extends Service {
consumptionTracker = ConsumptionTracker.get(this);
consumptionCloudClient = new ConsumptionCloudClient(getApplicationContext());
chargeSessionTracker = ChargeSessionTracker.get(getApplicationContext());
wifi = new WifiAutoConnect(getApplicationContext());
telemetryUploader = new TelemetryUploader(getApplicationContext());
steeringKeys = new SteeringKeyListener(this, prefs);
steeringKeys.register();
@@ -169,12 +167,6 @@ public class AbrpUploadService extends Service {
chargeSessionTracker.sample(vehicle);
consumptionCloudClient.syncIfDue(consumptionTracker, chargeSessionTracker, sampleMs);
// Keep the head unit on the phone hotspot — the sync needs a network, and this is
// exactly when being offline costs something. Rate-limited internally.
if (wifi != null && prefs.getBoolean(MainActivity.WIFI_AUTO_KEY, true)) {
wifi.ensureConnected(false);
}
UploadSettings settings = UploadSettings.from(prefs);
// Live data goes to the server, to ABRP directly, or both; with neither set up there is
// nowhere to send it.
@@ -35,12 +35,6 @@ import com.evsuite.hardware.telemetry.EnergyTelemetryReader;
public class MainActivity extends AppCompatActivity {
/**
* 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";
/** Navigation destinations in page order. Parallel to {@link #panes}. */
@@ -1,135 +0,0 @@
package com.evsuite.abrp;
import android.content.Context;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.util.Log;
import java.util.List;
/**
* Brings Wi-Fi up and joins a network the head unit already knows.
*
* The point is the phone hotspot: the car has the credentials saved, but the driver still
* has to walk into Settings and tap the network every time. This does that tap.
*
* <b>Why this works here and would not on a newer phone.</b> {@code getConfiguredNetworks}
* and {@code enableNetwork} were deprecated in API 29 and, from that release, return an
* empty list and refuse the call for ordinary apps — Android handed network choice to the
* user through a system picker. This head unit runs Android 9 (API 28), where both are
* still honoured, and the build is platform-signed, so it is the same UID family the
* Settings app itself uses. Neither fact is portable: on a Pixel this class would find
* nothing and change nothing. It is deliberately written to fail quietly if so.
*
* Nothing here creates or edits a network. It only enables one the unit was already
* configured with, which is why no password ever passes through this app.
*/
final class WifiAutoConnect {
private static final String TAG = "EVABRP.Wifi";
/** Re-checking faster than this achieves nothing: a join takes seconds to settle. */
private static final long MIN_ATTEMPT_INTERVAL_MS = 30_000L;
private final WifiManager wifi;
private long lastAttemptMs;
WifiAutoConnect(Context context) {
this.wifi = (WifiManager) context.getApplicationContext()
.getSystemService(Context.WIFI_SERVICE);
}
/** The SSID the unit is associated with, or null when it is not on Wi-Fi. */
String currentSsid() {
try {
if (wifi == null || !wifi.isWifiEnabled()) return null;
WifiInfo info = wifi.getConnectionInfo();
if (info == null) return null;
String ssid = info.getSSID();
if (ssid == null) return null;
// The framework quotes the SSID and reports this placeholder when it will not
// say — usually because location is off. Neither is a network name.
ssid = ssid.replace("\"", "").trim();
if (ssid.isEmpty() || "<unknown ssid>".equals(ssid) || "0x".equals(ssid)) return null;
return ssid;
} catch (Throwable t) {
return null;
}
}
/**
* One attempt to get onto a saved network, rate-limited.
*
* @param force ignore the rate limit — for the button, which a driver only presses
* because the automatic attempt has not worked yet.
* @return what happened, for the log and the UI. Never null.
*/
Result ensureConnected(boolean force) {
if (wifi == null) return Result.UNSUPPORTED;
String ssid = currentSsid();
if (ssid != null) return Result.alreadyOn(ssid);
long now = System.currentTimeMillis();
if (!force && now - lastAttemptMs < MIN_ATTEMPT_INTERVAL_MS) return Result.WAITING;
lastAttemptMs = now;
try {
if (!wifi.isWifiEnabled()) {
// Also gone for ordinary apps from API 29, and also still honoured here.
if (!wifi.setWifiEnabled(true)) return Result.CANNOT_ENABLE;
// The radio needs a moment before it will associate; the next tick retries.
return Result.ENABLING;
}
List<WifiConfiguration> saved = wifi.getConfiguredNetworks();
if (saved == null || saved.isEmpty()) return Result.NO_SAVED_NETWORKS;
// A scan would let us prefer a network that is actually in range, but the results
// are themselves throttled and location-gated. Asking the supplicant to pick from
// what it knows is both simpler and what the Settings screen effectively does.
wifi.reconnect();
for (WifiConfiguration c : saved) {
if (c == null) continue;
// Second argument true: disable the others for this attempt, so the
// supplicant commits to this one rather than drifting between them.
if (wifi.enableNetwork(c.networkId, true)) {
String name = c.SSID == null ? "?" : c.SSID.replace("\"", "").trim();
Log.i(TAG, "enableNetwork accepted for " + name);
return Result.attempting(name);
}
}
return Result.REFUSED;
} catch (SecurityException e) {
Log.w(TAG, "Wi-Fi connection refused: permission denied");
return Result.REFUSED;
} catch (Throwable t) {
Log.w(TAG, "connect attempt failed: " + t.getMessage());
return Result.REFUSED;
}
}
/** Outcome of one attempt. {@link #connected} is the only one that means we are online. */
static final class Result {
final boolean connected;
final String ssid;
final String message;
private Result(boolean connected, String ssid, String message) {
this.connected = connected;
this.ssid = ssid;
this.message = message;
}
static Result alreadyOn(String ssid) { return new Result(true, ssid, "Connected: " + ssid); }
static Result attempting(String ssid) { return new Result(false, ssid, "Connecting: " + ssid); }
static final Result UNSUPPORTED = new Result(false, null, "Wi-Fi cannot be managed");
static final Result WAITING = new Result(false, null, "Waiting…");
static final Result ENABLING = new Result(false, null, "Turning Wi-Fi on…");
static final Result CANNOT_ENABLE = new Result(false, null, "Could not turn Wi-Fi on");
static final Result NO_SAVED_NETWORKS = new Result(false, null, "No saved networks");
static final Result REFUSED = new Result(false, null, "The system refused");
}
}