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. * * Why this works here and would not on a newer phone. {@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() || "".equals(ssid) || "0x".equals(ssid)) return null; return ssid; } catch (Throwable t) { return null; } } 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 savedNetworkNames() { java.util.List names = new java.util.ArrayList<>(); try { if (wifi == null) return names; List 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. * * @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 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, "Bekleniyor…"); 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, "Sistem izin vermedi"); } }