From 8468ac508240e9808756a6660f0a9ba818a96e02 Mon Sep 17 00:00:00 2001 From: DK <117024745+Dhangofa@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:15:55 +0530 Subject: [PATCH 1/4] Avoid caching unverified 5G capability results MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Kept Android 11 as the minimum version for 5G capability detection. - Preserved the forward-compatible profile ID check for values 23 and higher. - Prevented empty default-network properties from being cached as 5G unsupported. - Prevented unparseable property values from producing a cached negative result. - Cached device capabilities only when at least one valid numeric profile was found. - Preserved Android 7–10 compatibility and existing legacy capability behavior. --- .../telephony/NetworkCapabilityResolver.java | 120 +++++++++++++----- 1 file changed, 85 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/com/dhangofa/networktoggle/telephony/NetworkCapabilityResolver.java b/app/src/main/java/com/dhangofa/networktoggle/telephony/NetworkCapabilityResolver.java index c92f1b4..82876eb 100644 --- a/app/src/main/java/com/dhangofa/networktoggle/telephony/NetworkCapabilityResolver.java +++ b/app/src/main/java/com/dhangofa/networktoggle/telephony/NetworkCapabilityResolver.java @@ -70,45 +70,95 @@ public NetworkCapabilities getCapabilities(ExecutionMode mode) { return finalCaps; } - private NetworkCapabilities fetchDeviceCapabilities(ExecutionMode mode) { - NetworkCapabilities cachedDevice = appPreferences.getDeviceCapabilities(); - if (cachedDevice != null) return cachedDevice; - + private NetworkCapabilities fetchDeviceCapabilities( + ExecutionMode mode + ) { + NetworkCapabilities cachedDevice = + appPreferences.getDeviceCapabilities(); + + if (cachedDevice != null) { + return cachedDevice; + } + + // 5G capability detection starts from Android 11. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + NetworkCapabilities legacyCapabilities = + new NetworkCapabilities( + true, + true, + true, + false + ); + + appPreferences.saveDeviceCapabilities( + legacyCapabilities + ); + + return legacyCapabilities; + } + + CommandExecutor executor = + CommandExecutorFactory.forMode(mode); + + if (executor == null) { + return NetworkCapabilities.assumeAll(); + } + + CommandResult result = executor.execute( + "getprop ro.telephony.default_network" + ); + + if (!result.isSuccess()) { + return NetworkCapabilities.assumeAll(); + } + + String output = result.getStdout(); + + // Empty output does not prove that 5G is unsupported. + if (output == null || output.trim().isEmpty()) { + return NetworkCapabilities.assumeAll(); + } + + boolean foundValidProfile = false; boolean supports5g = false; - boolean canCache = true; - - // Stage 1: Android Version Check (Android 11 / API 30+) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - CommandExecutor executor = CommandExecutorFactory.forMode(mode); - if (executor != null) { - // Stage 2: Hardware Ceiling Check - CommandResult result = executor.execute("getprop ro.telephony.default_network"); - if (result.isSuccess()) { - if (!result.getStdout().trim().isEmpty()) { - String[] values = result.getStdout().trim().split(","); - - // Check if ANY value globally supports 5G (>= 23) - for (String val : values) { - Integer parsed = ShellValueParser.extractFirstInt(val); - if (parsed != null && parsed >= 23) { - supports5g = true; - break; - } - } - } - } else { - canCache = false; - } - } else { - canCache = false; + + String[] values = output.trim().split(","); + + for (String value : values) { + Integer parsed = + ShellValueParser.extractFirstInt(value); + + if (parsed == null) { + continue; + } + + foundValidProfile = true; + + // Profile 23 and higher means 5G or newer capability. + if (parsed >= 23) { + supports5g = true; + break; } } - - NetworkCapabilities deviceCaps = new NetworkCapabilities(true, true, true, supports5g); - if (canCache) { - appPreferences.saveDeviceCapabilities(deviceCaps); + + // Do not cache an unverified negative result. + if (!foundValidProfile) { + return NetworkCapabilities.assumeAll(); } - return deviceCaps; + + NetworkCapabilities deviceCapabilities = + new NetworkCapabilities( + true, + true, + true, + supports5g + ); + + appPreferences.saveDeviceCapabilities( + deviceCapabilities + ); + + return deviceCapabilities; } private NetworkCapabilities fetchCarrierCapabilities(ExecutionMode mode, int slotIndex, String carrierName) { From 618d9709fd173e96ae187b20a6977b883ca8ed38 Mon Sep 17 00:00:00 2001 From: DK <117024745+Dhangofa@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:40:32 +0530 Subject: [PATCH 2/4] Prevent false carrier alias matches - Preserved the maintained LTE-and-above carrier registry. - Restricted short carrier aliases to exact matching. - Added whole-phrase matching for longer carrier labels. - Normalized common separators before phrase matching. - Prevented unrelated carriers from matching short aliases by substring. --- .../telephony/LteAndAboveCarrierRegistry.java | 68 +++++++++++-------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/dhangofa/networktoggle/telephony/LteAndAboveCarrierRegistry.java b/app/src/main/java/com/dhangofa/networktoggle/telephony/LteAndAboveCarrierRegistry.java index 4b99aea..add4fa9 100644 --- a/app/src/main/java/com/dhangofa/networktoggle/telephony/LteAndAboveCarrierRegistry.java +++ b/app/src/main/java/com/dhangofa/networktoggle/telephony/LteAndAboveCarrierRegistry.java @@ -57,37 +57,47 @@ public static boolean isLteAndAboveOnly(String carrierName) { if (carrierName == null || carrierName.trim().isEmpty()) { return false; } - String normalized = carrierName.toLowerCase().trim(); - - for (String name : INDIA_LTE_AND_ABOVE) { - if (normalized.equals(name) || normalized.contains(name)) return true; - } - - for (String name : USA_LTE_AND_ABOVE) { - if (normalized.equals(name) || normalized.contains(name)) return true; - } - - for (String name : JAPAN_LTE_AND_ABOVE) { - if (normalized.equals(name) || normalized.contains(name)) return true; - } - - for (String name : AUSTRALIA_LTE_AND_ABOVE) { - if (normalized.equals(name) || normalized.contains(name)) return true; - } - - for (String name : TAIWAN_LTE_AND_ABOVE) { - if (normalized.equals(name) || normalized.contains(name)) return true; - } - - for (String name : SINGAPORE_LTE_AND_ABOVE) { - if (normalized.equals(name) || normalized.contains(name)) return true; - } - - for (String name : SOUTHKOREA_LTE_AND_ABOVE) { - if (normalized.equals(name) || normalized.contains(name)) return true; + return matchesCarrier(normalized, INDIA_LTE_AND_ABOVE) || + matchesCarrier(normalized, USA_LTE_AND_ABOVE) || + matchesCarrier(normalized, JAPAN_LTE_AND_ABOVE) || + matchesCarrier(normalized, AUSTRALIA_LTE_AND_ABOVE) || + matchesCarrier(normalized, TAIWAN_LTE_AND_ABOVE) || + matchesCarrier(normalized, SINGAPORE_LTE_AND_ABOVE) || + matchesCarrier(normalized, SOUTHKOREA_LTE_AND_ABOVE); + } + private static boolean matchesCarrier(String carrierName, List carrierAliases) { + for (String alias : carrierAliases) { + String normalizedAlias = alias.toLowerCase().trim(); + + if (carrierName.equals(normalizedAlias)) { + return true; + } + // Short aliases must match exactly. + if (normalizedAlias.length() <= 3) { + continue; + } + // Longer aliases can match inside an extended carrier label. + if (containsWholePhrase(carrierName, normalizedAlias)) { + return true; + } } - + return false; } + + private static boolean containsWholePhrase(String carrierName, String alias) { + String normalizedCarrier = normalizeSeparators(carrierName); + String normalizedAlias = normalizeSeparators(alias); + + return (" " + normalizedCarrier + " ") + .contains(" " + normalizedAlias + " "); + } + + private static String normalizeSeparators(String value) { + return value.replace('-', ' ') + .replace('_', ' ') + .replaceAll("\\s+", " ") + .trim(); + } } From 72c68baf995006a713ec75f0bd5165fa62cad9be Mon Sep 17 00:00:00 2001 From: DK <117024745+Dhangofa@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:47:33 +0530 Subject: [PATCH 3/4] Update code format --- .../telephony/NetworkCapabilityResolver.java | 139 +++++++----------- 1 file changed, 52 insertions(+), 87 deletions(-) diff --git a/app/src/main/java/com/dhangofa/networktoggle/telephony/NetworkCapabilityResolver.java b/app/src/main/java/com/dhangofa/networktoggle/telephony/NetworkCapabilityResolver.java index 82876eb..8694662 100644 --- a/app/src/main/java/com/dhangofa/networktoggle/telephony/NetworkCapabilityResolver.java +++ b/app/src/main/java/com/dhangofa/networktoggle/telephony/NetworkCapabilityResolver.java @@ -70,97 +70,62 @@ public NetworkCapabilities getCapabilities(ExecutionMode mode) { return finalCaps; } - private NetworkCapabilities fetchDeviceCapabilities( - ExecutionMode mode - ) { - NetworkCapabilities cachedDevice = - appPreferences.getDeviceCapabilities(); - - if (cachedDevice != null) { - return cachedDevice; - } - - // 5G capability detection starts from Android 11. - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - NetworkCapabilities legacyCapabilities = - new NetworkCapabilities( - true, - true, - true, - false - ); - - appPreferences.saveDeviceCapabilities( - legacyCapabilities - ); - - return legacyCapabilities; - } - - CommandExecutor executor = - CommandExecutorFactory.forMode(mode); - - if (executor == null) { - return NetworkCapabilities.assumeAll(); - } - - CommandResult result = executor.execute( - "getprop ro.telephony.default_network" - ); - - if (!result.isSuccess()) { - return NetworkCapabilities.assumeAll(); - } - - String output = result.getStdout(); - - // Empty output does not prove that 5G is unsupported. - if (output == null || output.trim().isEmpty()) { - return NetworkCapabilities.assumeAll(); - } - - boolean foundValidProfile = false; - boolean supports5g = false; - - String[] values = output.trim().split(","); - - for (String value : values) { - Integer parsed = - ShellValueParser.extractFirstInt(value); - - if (parsed == null) { - continue; - } - - foundValidProfile = true; - - // Profile 23 and higher means 5G or newer capability. - if (parsed >= 23) { - supports5g = true; - break; - } + private NetworkCapabilities fetchDeviceCapabilities(ExecutionMode mode) { + NetworkCapabilities cachedDevice = appPreferences.getDeviceCapabilities(); + + if (cachedDevice != null) { + return cachedDevice; + } + + // 5G capability detection starts from Android 11. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + NetworkCapabilities legacyCapabilities = new NetworkCapabilities(true, true, true, false); + + appPreferences.saveDeviceCapabilities(legacyCapabilities); + return legacyCapabilities; + } + + CommandExecutor executor = CommandExecutorFactory.forMode(mode); + if (executor == null) { + return NetworkCapabilities.assumeAll(); + } + CommandResult result = executor.execute( "getprop ro.telephony.default_network" ); + if (!result.isSuccess()) { + return NetworkCapabilities.assumeAll(); + } + String output = result.getStdout(); + + // Empty output does not prove that 5G is unsupported. + if (output == null || output.trim().isEmpty()) { + return NetworkCapabilities.assumeAll(); + } + + boolean foundValidProfile = false; + boolean supports5g = false; + String[] values = output.trim().split(","); + for (String value : values) { + Integer parsed = ShellValueParser.extractFirstInt(value); + if (parsed == null) { + continue; } - - // Do not cache an unverified negative result. - if (!foundValidProfile) { - return NetworkCapabilities.assumeAll(); + foundValidProfile = true; + // Profile 23 and higher means 5G or newer capability. + if (parsed >= 23) { + supports5g = true; + break; } - - NetworkCapabilities deviceCapabilities = - new NetworkCapabilities( - true, - true, - true, - supports5g - ); - - appPreferences.saveDeviceCapabilities( - deviceCapabilities - ); - - return deviceCapabilities; } + // Do not cache a result when the property had no numeric profile. + if (!foundValidProfile) { + return NetworkCapabilities.assumeAll(); + } + + NetworkCapabilities deviceCapabilities = new NetworkCapabilities(true, true, true, supports5g); + appPreferences.saveDeviceCapabilities(deviceCapabilities); + return deviceCapabilities; +} + private NetworkCapabilities fetchCarrierCapabilities(ExecutionMode mode, int slotIndex, String carrierName) { CommandExecutor executor = CommandExecutorFactory.forMode(mode); if (executor == null) return NetworkCapabilities.assumeAll(); From 2fd1685b787fcd5641b69a1ee5c5b34cc88f76b0 Mon Sep 17 00:00:00 2001 From: DK <117024745+Dhangofa@users.noreply.github.com> Date: Tue, 18 Aug 2026 14:19:05 +0530 Subject: [PATCH 4/4] Cache only verified carrier capability results - Replaced the shell echo cache check with capability-source validation. - Required at least one relevant selected-slot CarrierConfig value before caching. - Kept maintained carrier registry matches as cacheable real-world evidence. - Prevented slot headers alone from validating default capability values. - Preserved optimistic behavior when CarrierConfig detection is unavailable. --- .../telephony/NetworkCapabilityResolver.java | 227 ++++++++++++------ 1 file changed, 151 insertions(+), 76 deletions(-) diff --git a/app/src/main/java/com/dhangofa/networktoggle/telephony/NetworkCapabilityResolver.java b/app/src/main/java/com/dhangofa/networktoggle/telephony/NetworkCapabilityResolver.java index 8694662..3de893b 100644 --- a/app/src/main/java/com/dhangofa/networktoggle/telephony/NetworkCapabilityResolver.java +++ b/app/src/main/java/com/dhangofa/networktoggle/telephony/NetworkCapabilityResolver.java @@ -13,6 +13,16 @@ public final class NetworkCapabilityResolver { private final AppPreferences appPreferences; private final SimResolver simResolver; + private static final class CarrierCapabilityResult { + final NetworkCapabilities capabilities; + final boolean cacheable; + + CarrierCapabilityResult(NetworkCapabilities capabilities, boolean cacheable) { + this.capabilities = capabilities; + this.cacheable = cacheable; + } + } + public NetworkCapabilityResolver(AppPreferences appPreferences, SimResolver simResolver) { this.appPreferences = appPreferences; this.simResolver = simResolver; @@ -50,7 +60,8 @@ public NetworkCapabilities getCapabilities(ExecutionMode mode) { NetworkCapabilities deviceCaps = fetchDeviceCapabilities(mode); // Stage 3 & 4: Slot-Specific Carrier Capabilities (Pass carrierName directly) - NetworkCapabilities carrierCaps = fetchCarrierCapabilities(mode, slotIndex, carrierName); + CarrierCapabilityResult carrierResult = fetchCarrierCapabilities(mode, slotIndex, carrierName); + NetworkCapabilities carrierCaps = carrierResult.capabilities; // Combine NetworkCapabilities finalCaps = new NetworkCapabilities( @@ -60,113 +71,177 @@ public NetworkCapabilities getCapabilities(ExecutionMode mode) { deviceCaps.supports5g && carrierCaps.supports5g ); - CommandExecutor executor = CommandExecutorFactory.forMode(mode); - if (executor != null) { - CommandResult pingResult = executor.execute("echo test"); - if (pingResult.isSuccess()) { - appPreferences.saveSlotCapabilities(slotIndex, subId, finalCaps); - } + if (carrierResult.cacheable) { + appPreferences.saveSlotCapabilities(slotIndex, subId, finalCaps); } return finalCaps; } private NetworkCapabilities fetchDeviceCapabilities(ExecutionMode mode) { - NetworkCapabilities cachedDevice = appPreferences.getDeviceCapabilities(); - - if (cachedDevice != null) { - return cachedDevice; - } - - // 5G capability detection starts from Android 11. - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - NetworkCapabilities legacyCapabilities = new NetworkCapabilities(true, true, true, false); - - appPreferences.saveDeviceCapabilities(legacyCapabilities); - return legacyCapabilities; - } - - CommandExecutor executor = CommandExecutorFactory.forMode(mode); - if (executor == null) { - return NetworkCapabilities.assumeAll(); - } - CommandResult result = executor.execute( "getprop ro.telephony.default_network" ); - if (!result.isSuccess()) { - return NetworkCapabilities.assumeAll(); - } - String output = result.getStdout(); - - // Empty output does not prove that 5G is unsupported. - if (output == null || output.trim().isEmpty()) { - return NetworkCapabilities.assumeAll(); - } - - boolean foundValidProfile = false; - boolean supports5g = false; - String[] values = output.trim().split(","); - for (String value : values) { - Integer parsed = ShellValueParser.extractFirstInt(value); - if (parsed == null) { - continue; + NetworkCapabilities cachedDevice = appPreferences.getDeviceCapabilities(); + + if (cachedDevice != null) { + return cachedDevice; } - foundValidProfile = true; - // Profile 23 and higher means 5G or newer capability. - if (parsed >= 23) { - supports5g = true; - break; + + // 5G capability detection starts from Android 11. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + NetworkCapabilities legacyCapabilities = new NetworkCapabilities(true, true, true, false); + + appPreferences.saveDeviceCapabilities(legacyCapabilities); + return legacyCapabilities; } + + CommandExecutor executor = CommandExecutorFactory.forMode(mode); + if (executor == null) { + return NetworkCapabilities.assumeAll(); + } + CommandResult result = executor.execute( "getprop ro.telephony.default_network" ); + if (!result.isSuccess()) { + return NetworkCapabilities.assumeAll(); + } + String output = result.getStdout(); + + // Empty output does not prove that 5G is unsupported. + if (output == null || output.trim().isEmpty()) { + return NetworkCapabilities.assumeAll(); + } + + boolean foundValidProfile = false; + boolean supports5g = false; + String[] values = output.trim().split(","); + for (String value : values) { + Integer parsed = ShellValueParser.extractFirstInt(value); + if (parsed == null) { + continue; + } + foundValidProfile = true; + // Profile 23 and higher means 5G or newer capability. + if (parsed >= 23) { + supports5g = true; + break; + } + } + + // Do not cache a result when the property had no numeric profile. + if (!foundValidProfile) { + return NetworkCapabilities.assumeAll(); + } + + NetworkCapabilities deviceCapabilities = new NetworkCapabilities(true, true, true, supports5g); + appPreferences.saveDeviceCapabilities(deviceCapabilities); + return deviceCapabilities; } - // Do not cache a result when the property had no numeric profile. - if (!foundValidProfile) { - return NetworkCapabilities.assumeAll(); - } - - NetworkCapabilities deviceCapabilities = new NetworkCapabilities(true, true, true, supports5g); - appPreferences.saveDeviceCapabilities(deviceCapabilities); - return deviceCapabilities; -} - - private NetworkCapabilities fetchCarrierCapabilities(ExecutionMode mode, int slotIndex, String carrierName) { + private CarrierCapabilityResult fetchCarrierCapabilities( + ExecutionMode mode, + int slotIndex, + String carrierName + ) { CommandExecutor executor = CommandExecutorFactory.forMode(mode); - if (executor == null) return NetworkCapabilities.assumeAll(); - - // Stage 3: Carrier Config XML Verification - String command = "dumpsys carrier_config | grep -E 'Phone Id|hide_enable_2g_bool|carrier_supports_2g_bool|hide_enable_3g_bool|carrier_supports_3g_bool|carrier_nr_availabilities_int_array'"; + + if (executor == null) { + return new CarrierCapabilityResult( + NetworkCapabilities.assumeAll(), + false + ); + } + + // Stage 3: Carrier Config XML verification. + String command = + "dumpsys carrier_config | grep -E " + + "'Phone Id|" + + "hide_enable_2g_bool|" + + "carrier_supports_2g_bool|" + + "hide_enable_3g_bool|" + + "carrier_supports_3g_bool|" + + "carrier_nr_availabilities_int_array'"; + CommandResult result = executor.execute(command); - + boolean supports2g = true; boolean supports3g = true; boolean supports5g = true; - + boolean foundCarrierSetting = false; + if (result.isSuccess() && !result.getStdout().trim().isEmpty()) { String[] lines = result.getStdout().split("\\n"); boolean inTargetSlot = false; - + for (String line : lines) { String trimmed = line.trim(); + if (trimmed.startsWith("Phone Id =")) { Integer currentSlot = ShellValueParser.extractFirstInt(trimmed); - inTargetSlot = (currentSlot != null && currentSlot == slotIndex); - } else if (inTargetSlot) { - if (trimmed.contains("hide_enable_2g_bool = true") || trimmed.contains("carrier_supports_2g_bool = false")) { + inTargetSlot = currentSlot != null && currentSlot == slotIndex; + continue; + } + + if (!inTargetSlot) { + continue; + } + + if (trimmed.startsWith("hide_enable_2g_bool =")) { + foundCarrierSetting = true; + + if (trimmed.endsWith("true")) { + supports2g = false; + } + } + + if (trimmed.startsWith("carrier_supports_2g_bool =")) { + foundCarrierSetting = true; + + if (trimmed.endsWith("false")) { supports2g = false; } - if (trimmed.contains("hide_enable_3g_bool = true") || trimmed.contains("carrier_supports_3g_bool = false")) { + } + + if (trimmed.startsWith("hide_enable_3g_bool =")) { + foundCarrierSetting = true; + + if (trimmed.endsWith("true")) { + supports3g = false; + } + } + + if (trimmed.startsWith("carrier_supports_3g_bool =")) { + foundCarrierSetting = true; + + if (trimmed.endsWith("false")) { supports3g = false; } - if (trimmed.startsWith("carrier_nr_availabilities_int_array = []")) { + } + + if (trimmed.startsWith("carrier_nr_availabilities_int_array =")) { + foundCarrierSetting = true; + + if (trimmed.endsWith("[]")) { supports5g = false; } } } } - - // Stage 4: Smart Blocklist Check - if (LteAndAboveCarrierRegistry.isLteAndAboveOnly(carrierName)) { + + // Stage 4: maintained real-world carrier restrictions. + boolean registryMatched = + LteAndAboveCarrierRegistry.isLteAndAboveOnly(carrierName); + + if (registryMatched) { supports2g = false; supports3g = false; } - - return new NetworkCapabilities(supports2g, supports3g, true, supports5g); + + NetworkCapabilities capabilities = + new NetworkCapabilities( + supports2g, + supports3g, + true, + supports5g + ); + + boolean cacheable = foundCarrierSetting || registryMatched; + + return new CarrierCapabilityResult(capabilities, cacheable); } }