From 216eb1f5d4da9a2b84fd43e31e4c4769a53c21b4 Mon Sep 17 00:00:00 2001 From: DK <117024745+Dhangofa@users.noreply.github.com> Date: Tue, 18 Aug 2026 17:24:49 +0530 Subject: [PATCH 01/14] Added Legacy values for NetWorkmode LEGACY MODE VALUES - 5G Only: 23 - Preferred 5G: 33 - 4G Only: 11 - Preferred 4G: 9 - Preferred 3G: 0 - 2G Only: 1 --- .../networktoggle/model/NetworkMode.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/dhangofa/networktoggle/model/NetworkMode.java b/app/src/main/java/com/dhangofa/networktoggle/model/NetworkMode.java index 2f0d0ce..42573f3 100644 --- a/app/src/main/java/com/dhangofa/networktoggle/model/NetworkMode.java +++ b/app/src/main/java/com/dhangofa/networktoggle/model/NetworkMode.java @@ -1,26 +1,29 @@ package com.dhangofa.networktoggle.model; public enum NetworkMode { - UNKNOWN(0, "Unknown", "Tap to Set 4G", "?", null), - FOUR_G_ONLY(1, "4G Only", "4G Only", "4G", "1000000000000"), - FIVE_G_ONLY(2, "5G Only", "5G Only", "5G", "10000000000000000000"), - PREFERRED_5G(3, "Preferred 5G", "Pref 5G", "P5G", "11011111101111111111"), - PREFERRED_4G(4, "Preferred 4G", "Pref 4G", "P4G", "1011111101111111111"), - PREFERRED_3G(5, "Preferred 3G", "Pref 3G", "P3G", "11110101111111111"), - TWO_G_ONLY(6, "2G Only", "2G Only", "2G", "1000000000000011"); + UNKNOWN(0, "Unknown", "Tap to Set 4G", "?", null, -1), + FOUR_G_ONLY(1, "4G Only", "4G Only", "4G", "1000000000000", 11), + FIVE_G_ONLY(2, "5G Only", "5G Only", "5G", "10000000000000000000", 23), + PREFERRED_5G(3, "Preferred 5G", "Pref 5G", "P5G", "11011111101111111111", 33), + PREFERRED_4G(4, "Preferred 4G", "Pref 4G", "P4G", "1011111101111111111", 9), + PREFERRED_3G(5, "Preferred 3G", "Pref 3G", "P3G", "11110101111111111", 0), + TWO_G_ONLY(6, "2G Only", "2G Only", "2G", "1000000000000011", 1); private final int stateValue; private final String displayName; private final String tileLabel; private final String iconText; private final String binaryMask; + private final int legacyMode; - NetworkMode(int stateValue, String displayName, String tileLabel, String iconText, String binaryMask) { + NetworkMode(int stateValue, String displayName, String tileLabel, + String iconText, String binaryMask, int legacyMode) { this.stateValue = stateValue; this.displayName = displayName; this.tileLabel = tileLabel; this.iconText = iconText; this.binaryMask = binaryMask; + this.legacyMode = legacyMode; } public int getStateValue() { return stateValue; } @@ -28,6 +31,7 @@ public enum NetworkMode { public String getTileLabel() { return tileLabel; } public String getIconText() { return iconText; } public String getBinaryMask() { return binaryMask; } + public int getLegacyMode() { return legacyMode; } public static NetworkMode fromStateValue(int value) { for (NetworkMode mode : values()) { @@ -70,8 +74,8 @@ public static NetworkMode fromLegacyMode(Integer legacyMode) { case 11: return FOUR_G_ONLY; case 23: return FIVE_G_ONLY; case 33: return PREFERRED_5G; - case 9: case 8: + case 9: case 10: case 12: case 15: From 8285907396a32aaa45db518758ef679e2179452d Mon Sep 17 00:00:00 2001 From: DK <117024745+Dhangofa@users.noreply.github.com> Date: Tue, 18 Aug 2026 17:45:36 +0530 Subject: [PATCH 02/14] Exposes the existing apllication context to telephony package controllers - Added explicit null handling for the active subscription list - Exposes the existing apllication context get context --- .../networktoggle/telephony/SimResolver.java | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/dhangofa/networktoggle/telephony/SimResolver.java b/app/src/main/java/com/dhangofa/networktoggle/telephony/SimResolver.java index ba0dd4e..c138086 100644 --- a/app/src/main/java/com/dhangofa/networktoggle/telephony/SimResolver.java +++ b/app/src/main/java/com/dhangofa/networktoggle/telephony/SimResolver.java @@ -14,13 +14,14 @@ import com.dhangofa.networktoggle.model.ExecutionMode; import com.dhangofa.networktoggle.model.TargetSim; +import java.util.List; + public final class SimResolver { public static final int INVALID_SLOT_INDEX = -1; public static final int INVALID_SUB_ID = -1; private final Context context; private final AppPreferences appPreferences; - // Data class to hold all extracted variables in one place public static class SimInfo { public final int subId; @@ -39,17 +40,15 @@ public SimResolver(Context context, AppPreferences appPreferences) { this.appPreferences = appPreferences; } - /** - * Resolves IDs using Native APIs first. - * Silently falls back to Shell commands if permission is denied or device is too old. - */ + Context getContext() { + return context; + } + public SimInfo resolveTargetSimInfo(ExecutionMode executionMode) { TargetSim targetSim = appPreferences.getTargetSim(); - int targetSubId = INVALID_SUB_ID; int targetSlotIndex = INVALID_SLOT_INDEX; String carrierName = ""; - // 1. Safe Native APIs (No permission required) if (targetSim.isAuto()) { targetSubId = SubscriptionManager.getDefaultDataSubscriptionId(); @@ -62,10 +61,10 @@ public SimInfo resolveTargetSimInfo(ExecutionMode executionMode) { } else { targetSlotIndex = targetSim.getManualSlotIndex(); } - // 2. Protected Native APIs (Requires READ_PHONE_STATE) - boolean hasPermission = context.checkSelfPermission(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED; - + boolean hasPermission = context.checkSelfPermission(Manifest.permission.READ_PHONE_STATE) + == PackageManager.PERMISSION_GRANTED; + if (hasPermission) { SubscriptionManager sm = context.getSystemService(SubscriptionManager.class); if (sm != null) { @@ -77,23 +76,25 @@ public SimInfo resolveTargetSimInfo(ExecutionMode executionMode) { if (!isValidSlotIndex(targetSlotIndex)) targetSlotIndex = info.getSimSlotIndex(); } } else if (isValidSlotIndex(targetSlotIndex)) { - for (SubscriptionInfo info : sm.getActiveSubscriptionInfoList()) { - if (info.getSimSlotIndex() == targetSlotIndex) { - targetSubId = info.getSubscriptionId(); - if (info.getCarrierName() != null) carrierName = info.getCarrierName().toString(); - break; + List infos = sm.getActiveSubscriptionInfoList(); + if (infos != null) { + for (SubscriptionInfo info : infos) { + if (info.getSimSlotIndex() == targetSlotIndex) { + targetSubId = info.getSubscriptionId(); + if (info.getCarrierName() != null) carrierName = info.getCarrierName().toString(); + break; + } } } } } catch (Exception ignored) {} } } - // 3. Shell Fallback (If missing permission, or older Android OS failed to map) if (!isValidSubId(targetSubId) || !isValidSlotIndex(targetSlotIndex) || carrierName.isEmpty()) { String command = "dumpsys isub | grep -E \"\\{id=[0-9]+ .*simSlotIndex=\""; CommandExecutor executor = CommandExecutorFactory.forMode(executionMode); - + if (executor != null) { CommandResult result = executor.execute(command); if (result.isSuccess() && !result.getStdout().trim().isEmpty()) { @@ -118,7 +119,6 @@ public SimInfo resolveTargetSimInfo(ExecutionMode executionMode) { } } } - // 4. Final Validation if (isValidSubId(targetSubId) && isValidSlotIndex(targetSlotIndex)) { if (targetSim.isAuto()) appPreferences.setAutoSimError(false); @@ -128,7 +128,6 @@ public SimInfo resolveTargetSimInfo(ExecutionMode executionMode) { if (targetSim.isAuto()) appPreferences.setAutoSimError(true); return null; } - // Keeping backwards compatibility for existing app logic public int resolveTargetSlotIndex(ExecutionMode executionMode) { SimInfo info = resolveTargetSimInfo(executionMode); From 49bc1659c50cdd8ba8f8113920b176d6e240fbed Mon Sep 17 00:00:00 2001 From: DK <117024745+Dhangofa@users.noreply.github.com> Date: Tue, 18 Aug 2026 17:51:31 +0530 Subject: [PATCH 03/14] Dispatch network switching by android versions - Routed Android 7 to 11 mode changes through the legacy backend - Kept Android 12+ mode changes the existing allowed-network-types backend --- .../telephony/NetworkModeController.java | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/dhangofa/networktoggle/telephony/NetworkModeController.java b/app/src/main/java/com/dhangofa/networktoggle/telephony/NetworkModeController.java index 752b678..52bb844 100644 --- a/app/src/main/java/com/dhangofa/networktoggle/telephony/NetworkModeController.java +++ b/app/src/main/java/com/dhangofa/networktoggle/telephony/NetworkModeController.java @@ -1,5 +1,7 @@ package com.dhangofa.networktoggle.telephony; +import android.os.Build; + import com.dhangofa.networktoggle.command.CommandExecutor; import com.dhangofa.networktoggle.command.CommandExecutorFactory; import com.dhangofa.networktoggle.model.CommandResult; @@ -8,33 +10,32 @@ public final class NetworkModeController { private final SimResolver simResolver; + private final LegacyNetworkModeController legacyController; public NetworkModeController(SimResolver simResolver) { this.simResolver = simResolver; + this.legacyController = new LegacyNetworkModeController( + simResolver.getContext(), simResolver); } - public CommandResult apply( - NetworkMode networkMode, - ExecutionMode executionMode - ) { - if (networkMode == null - || networkMode == NetworkMode.UNKNOWN + public CommandResult apply(NetworkMode networkMode, ExecutionMode executionMode) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + return legacyController.apply(networkMode, executionMode); + } + + if (networkMode == null || networkMode == NetworkMode.UNKNOWN || networkMode.getBinaryMask() == null) { return CommandResult.failed("", "Invalid network mode selected."); } int slotIndex = simResolver.resolveTargetSlotIndex(executionMode); if (!simResolver.isValidSlotIndex(slotIndex)) { - return CommandResult.failed( - "", - "Unable to resolve the target physical SIM slot." - ); + return CommandResult.failed("", + "Unable to resolve the target physical SIM slot."); } String command = "cmd phone set-allowed-network-types-for-users -s " - + slotIndex - + " " - + networkMode.getBinaryMask(); + + slotIndex + " " + networkMode.getBinaryMask(); CommandExecutor executor = CommandExecutorFactory.forMode(executionMode); if (executor == null) { From 22e87c9d7ffb40f9dda79bc0ac323ee57f934b61 Mon Sep 17 00:00:00 2001 From: DK <117024745+Dhangofa@users.noreply.github.com> Date: Tue, 18 Aug 2026 17:56:32 +0530 Subject: [PATCH 04/14] Add Android 7 to 11 network mode controllers - Added a dedicated legacy network mode backend. - Added transactional mode writes and cellular modem restart. - Added airplane mode radio-list preservation and restoration. - Added clean up traps and post operation verification. --- .../LegacyNetworkModeController.java | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 app/src/main/java/com/dhangofa/networktoggle/telephony/LegacyNetworkModeController.java diff --git a/app/src/main/java/com/dhangofa/networktoggle/telephony/LegacyNetworkModeController.java b/app/src/main/java/com/dhangofa/networktoggle/telephony/LegacyNetworkModeController.java new file mode 100644 index 0000000..6e98010 --- /dev/null +++ b/app/src/main/java/com/dhangofa/networktoggle/telephony/LegacyNetworkModeController.java @@ -0,0 +1,181 @@ +package com.dhangofa.networktoggle.telephony; + +import android.content.Context; +import android.provider.Settings; +import android.telephony.TelephonyManager; + +import com.dhangofa.networktoggle.command.CommandExecutor; +import com.dhangofa.networktoggle.command.CommandExecutorFactory; +import com.dhangofa.networktoggle.model.CommandResult; +import com.dhangofa.networktoggle.model.ExecutionMode; +import com.dhangofa.networktoggle.model.NetworkMode; + +final class LegacyNetworkModeController { + private static final int MODEM_RESTART_DELAY_SECONDS = 2; + + private final Context context; + private final SimResolver simResolver; + + LegacyNetworkModeController(Context context, SimResolver simResolver) { + this.context = context.getApplicationContext(); + this.simResolver = simResolver; + } + + CommandResult apply(NetworkMode networkMode, ExecutionMode executionMode) { + if (networkMode == null || networkMode == NetworkMode.UNKNOWN + || networkMode.getLegacyMode() < 0) { + return CommandResult.failed("", "Invalid legacy network mode selected."); + } + + if (isAirplaneModeEnabled()) { + return CommandResult.failed("", + "Disable Airplane mode before changing the network mode."); + } + + SimResolver.SimInfo simInfo = simResolver.resolveTargetSimInfo(executionMode); + if (simInfo == null || !simResolver.isValidSlotIndex(simInfo.slotIndex) + || !simResolver.isValidSubId(simInfo.subId)) { + return CommandResult.failed("", "Unable to resolve the target SIM."); + } + + Integer[] currentModes = readCombinedModes(); + int requiredEntries = getRequiredCombinedEntries(simInfo.slotIndex); + if (currentModes.length < requiredEntries) { + return CommandResult.failed("", + "Unable to preserve the current mode of every SIM slot."); + } + + for (int i = 0; i < requiredEntries; i++) { + if (currentModes[i] == null) { + return CommandResult.failed("", + "Unable to read the current mode for SIM slot " + (i + 1) + "."); + } + } + + currentModes[simInfo.slotIndex] = networkMode.getLegacyMode(); + String combinedModes = buildCombinedModes(currentModes, requiredEntries); + String originalRadios = readAirplaneModeRadios(); + String restoreRadios = originalRadios == null + ? "settings delete global airplane_mode_radios" + : "settings put global airplane_mode_radios " + shellEscape(originalRadios); + + String command = buildTransactionCommand( + simInfo.subId, + networkMode.getLegacyMode(), + combinedModes, + restoreRadios + ); + + CommandExecutor executor = CommandExecutorFactory.forMode(executionMode); + if (executor == null) { + return CommandResult.failed(command, "No execution mode selected."); + } + + CommandResult result = executor.execute(command); + if (!result.isSuccess()) return result; + + return verifyResult(command, simInfo, networkMode.getLegacyMode(), + originalRadios, requiredEntries); + } + + private Integer[] readCombinedModes() { + String value = Settings.Global.getString( + context.getContentResolver(), "preferred_network_mode"); + if (value == null || value.trim().isEmpty() + || value.trim().equalsIgnoreCase("null")) { + return new Integer[0]; + } + + String[] parts = value.trim().split(",", -1); + Integer[] modes = new Integer[parts.length]; + for (int i = 0; i < parts.length; i++) { + modes[i] = ShellValueParser.extractFirstInt(parts[i]); + } + return modes; + } + + private int getRequiredCombinedEntries(int targetSlot) { + TelephonyManager manager = (TelephonyManager) + context.getSystemService(Context.TELEPHONY_SERVICE); + int phoneCount = manager == null ? 1 : manager.getPhoneCount(); + return Math.max(targetSlot + 1, Math.min(phoneCount, 2)); + } + + private String buildCombinedModes(Integer[] modes, int count) { + StringBuilder value = new StringBuilder(); + for (int i = 0; i < count; i++) { + if (i > 0) value.append(','); + value.append(modes[i]); + } + return value.toString(); + } + + private boolean isAirplaneModeEnabled() { + return Settings.Global.getInt( + context.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0) == 1; + } + + private String readAirplaneModeRadios() { + return Settings.Global.getString( + context.getContentResolver(), Settings.Global.AIRPLANE_MODE_RADIOS); + } + + private String buildTransactionCommand(int subId, int requestedMode, + String combinedModes, String restoreRadios) { + return "cleanup() { " + + "cmd connectivity airplane-mode disable >/dev/null 2>&1; " + + restoreRadios + " >/dev/null 2>&1; " + + "}; " + + "trap cleanup EXIT INT TERM; " + + "settings put global airplane_mode_radios cell || exit 20; " + + "settings put global preferred_network_mode" + subId + " " + + requestedMode + " || exit 21; " + + "settings put global preferred_network_mode " + combinedModes + + " || exit 22; " + + "cmd connectivity airplane-mode enable || exit 23; " + + "sleep " + MODEM_RESTART_DELAY_SECONDS + "; " + + "cmd connectivity airplane-mode disable || exit 24; " + + "sleep " + MODEM_RESTART_DELAY_SECONDS + "; " + + restoreRadios + " || exit 25; " + + "trap - EXIT INT TERM; " + + "exit 0"; + } + + private CommandResult verifyResult(String command, SimResolver.SimInfo simInfo, + int requestedMode, String originalRadios, + int requiredEntries) { + String subValue = Settings.Global.getString( + context.getContentResolver(), + "preferred_network_mode" + simInfo.subId); + Integer finalSubMode = ShellValueParser.extractFirstInt(subValue); + Integer[] finalCombinedModes = readCombinedModes(); + String finalRadios = readAirplaneModeRadios(); + + if (isAirplaneModeEnabled()) { + return CommandResult.failed(command, + "Legacy mode was written, but Airplane mode is still enabled."); + } + if (finalSubMode == null || finalSubMode != requestedMode) { + return CommandResult.failed(command, + "Legacy per-subscription mode verification failed."); + } + if (finalCombinedModes.length < requiredEntries + || finalCombinedModes[simInfo.slotIndex] == null + || finalCombinedModes[simInfo.slotIndex] != requestedMode) { + return CommandResult.failed(command, + "Legacy combined mode verification failed."); + } + if (originalRadios == null ? finalRadios != null + : !originalRadios.equals(finalRadios)) { + return CommandResult.failed(command, + "Original Airplane mode radio configuration was not restored."); + } + + return CommandResult.completed(command, 0, + "Legacy network mode applied and verified.", ""); + } + + private String shellEscape(String value) { + return "'" + value.replace("'", "'\\''") + "'"; + } +} From 1dba8c02eb8206f0d42642497fa41af42385e21e Mon Sep 17 00:00:00 2001 From: DK <117024745+Dhangofa@users.noreply.github.com> Date: Tue, 18 Aug 2026 19:32:24 +0530 Subject: [PATCH 05/14] Removed the unnecessary sleep in the legacy traps --- .../networktoggle/telephony/LegacyNetworkModeController.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/com/dhangofa/networktoggle/telephony/LegacyNetworkModeController.java b/app/src/main/java/com/dhangofa/networktoggle/telephony/LegacyNetworkModeController.java index 6e98010..e3d9720 100644 --- a/app/src/main/java/com/dhangofa/networktoggle/telephony/LegacyNetworkModeController.java +++ b/app/src/main/java/com/dhangofa/networktoggle/telephony/LegacyNetworkModeController.java @@ -11,7 +11,6 @@ import com.dhangofa.networktoggle.model.NetworkMode; final class LegacyNetworkModeController { - private static final int MODEM_RESTART_DELAY_SECONDS = 2; private final Context context; private final SimResolver simResolver; @@ -133,9 +132,8 @@ private String buildTransactionCommand(int subId, int requestedMode, + "settings put global preferred_network_mode " + combinedModes + " || exit 22; " + "cmd connectivity airplane-mode enable || exit 23; " - + "sleep " + MODEM_RESTART_DELAY_SECONDS + "; " + + "sleep 1; " + "cmd connectivity airplane-mode disable || exit 24; " - + "sleep " + MODEM_RESTART_DELAY_SECONDS + "; " + restoreRadios + " || exit 25; " + "trap - EXIT INT TERM; " + "exit 0"; From 5964ed2401c2e9c23717ee6b1a3af25b6d647945 Mon Sep 17 00:00:00 2001 From: DK <117024745+Dhangofa@users.noreply.github.com> Date: Wed, 19 Aug 2026 00:11:31 +0530 Subject: [PATCH 06/14] Migrate legacy network toggle to Java payload execution - Removed all legacy shell scripts that relied on settings put global and Airplane Mode toggling to force network changes. - Added dynamic path resolution (context.getApplicationInfo().sourceDir) to locate the app's base APK at runtime. - Refactored the apply() method to format and execute an app_process shell command using the APK as the CLASSPATH and targeting NetworkModePayload. - Simplified the legacy execution flow and error handling, relying on the Java payload's internal success/failure outputs. --- .../LegacyNetworkModeController.java | 152 ++---------------- 1 file changed, 11 insertions(+), 141 deletions(-) diff --git a/app/src/main/java/com/dhangofa/networktoggle/telephony/LegacyNetworkModeController.java b/app/src/main/java/com/dhangofa/networktoggle/telephony/LegacyNetworkModeController.java index e3d9720..30c6be6 100644 --- a/app/src/main/java/com/dhangofa/networktoggle/telephony/LegacyNetworkModeController.java +++ b/app/src/main/java/com/dhangofa/networktoggle/telephony/LegacyNetworkModeController.java @@ -1,9 +1,6 @@ package com.dhangofa.networktoggle.telephony; import android.content.Context; -import android.provider.Settings; -import android.telephony.TelephonyManager; - import com.dhangofa.networktoggle.command.CommandExecutor; import com.dhangofa.networktoggle.command.CommandExecutorFactory; import com.dhangofa.networktoggle.model.CommandResult; @@ -11,7 +8,6 @@ import com.dhangofa.networktoggle.model.NetworkMode; final class LegacyNetworkModeController { - private final Context context; private final SimResolver simResolver; @@ -26,154 +22,28 @@ CommandResult apply(NetworkMode networkMode, ExecutionMode executionMode) { return CommandResult.failed("", "Invalid legacy network mode selected."); } - if (isAirplaneModeEnabled()) { - return CommandResult.failed("", - "Disable Airplane mode before changing the network mode."); - } - SimResolver.SimInfo simInfo = simResolver.resolveTargetSimInfo(executionMode); if (simInfo == null || !simResolver.isValidSlotIndex(simInfo.slotIndex) || !simResolver.isValidSubId(simInfo.subId)) { return CommandResult.failed("", "Unable to resolve the target SIM."); } - - Integer[] currentModes = readCombinedModes(); - int requiredEntries = getRequiredCombinedEntries(simInfo.slotIndex); - if (currentModes.length < requiredEntries) { - return CommandResult.failed("", - "Unable to preserve the current mode of every SIM slot."); - } - - for (int i = 0; i < requiredEntries; i++) { - if (currentModes[i] == null) { - return CommandResult.failed("", - "Unable to read the current mode for SIM slot " + (i + 1) + "."); - } - } - - currentModes[simInfo.slotIndex] = networkMode.getLegacyMode(); - String combinedModes = buildCombinedModes(currentModes, requiredEntries); - String originalRadios = readAirplaneModeRadios(); - String restoreRadios = originalRadios == null - ? "settings delete global airplane_mode_radios" - : "settings put global airplane_mode_radios " + shellEscape(originalRadios); - - String command = buildTransactionCommand( - simInfo.subId, - networkMode.getLegacyMode(), - combinedModes, - restoreRadios - ); - + + String apkPath = context.getApplicationInfo().sourceDir; + String className = "com.dhangofa.networktoggle.telephony.NetworkModePayload"; + + String command = "CLASSPATH=\"" + apkPath + "\" app_process /system/bin " + className + " " + simInfo.subId + " " + networkMode.getLegacyMode(); + CommandExecutor executor = CommandExecutorFactory.forMode(executionMode); if (executor == null) { return CommandResult.failed(command, "No execution mode selected."); } - + CommandResult result = executor.execute(command); - if (!result.isSuccess()) return result; - - return verifyResult(command, simInfo, networkMode.getLegacyMode(), - originalRadios, requiredEntries); - } - - private Integer[] readCombinedModes() { - String value = Settings.Global.getString( - context.getContentResolver(), "preferred_network_mode"); - if (value == null || value.trim().isEmpty() - || value.trim().equalsIgnoreCase("null")) { - return new Integer[0]; - } - - String[] parts = value.trim().split(",", -1); - Integer[] modes = new Integer[parts.length]; - for (int i = 0; i < parts.length; i++) { - modes[i] = ShellValueParser.extractFirstInt(parts[i]); - } - return modes; - } - - private int getRequiredCombinedEntries(int targetSlot) { - TelephonyManager manager = (TelephonyManager) - context.getSystemService(Context.TELEPHONY_SERVICE); - int phoneCount = manager == null ? 1 : manager.getPhoneCount(); - return Math.max(targetSlot + 1, Math.min(phoneCount, 2)); - } - - private String buildCombinedModes(Integer[] modes, int count) { - StringBuilder value = new StringBuilder(); - for (int i = 0; i < count; i++) { - if (i > 0) value.append(','); - value.append(modes[i]); + if (!result.isSuccess()) { + return result; } - return value.toString(); - } - - private boolean isAirplaneModeEnabled() { - return Settings.Global.getInt( - context.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0) == 1; - } - - private String readAirplaneModeRadios() { - return Settings.Global.getString( - context.getContentResolver(), Settings.Global.AIRPLANE_MODE_RADIOS); - } - - private String buildTransactionCommand(int subId, int requestedMode, - String combinedModes, String restoreRadios) { - return "cleanup() { " - + "cmd connectivity airplane-mode disable >/dev/null 2>&1; " - + restoreRadios + " >/dev/null 2>&1; " - + "}; " - + "trap cleanup EXIT INT TERM; " - + "settings put global airplane_mode_radios cell || exit 20; " - + "settings put global preferred_network_mode" + subId + " " - + requestedMode + " || exit 21; " - + "settings put global preferred_network_mode " + combinedModes - + " || exit 22; " - + "cmd connectivity airplane-mode enable || exit 23; " - + "sleep 1; " - + "cmd connectivity airplane-mode disable || exit 24; " - + restoreRadios + " || exit 25; " - + "trap - EXIT INT TERM; " - + "exit 0"; - } - - private CommandResult verifyResult(String command, SimResolver.SimInfo simInfo, - int requestedMode, String originalRadios, - int requiredEntries) { - String subValue = Settings.Global.getString( - context.getContentResolver(), - "preferred_network_mode" + simInfo.subId); - Integer finalSubMode = ShellValueParser.extractFirstInt(subValue); - Integer[] finalCombinedModes = readCombinedModes(); - String finalRadios = readAirplaneModeRadios(); - - if (isAirplaneModeEnabled()) { - return CommandResult.failed(command, - "Legacy mode was written, but Airplane mode is still enabled."); - } - if (finalSubMode == null || finalSubMode != requestedMode) { - return CommandResult.failed(command, - "Legacy per-subscription mode verification failed."); - } - if (finalCombinedModes.length < requiredEntries - || finalCombinedModes[simInfo.slotIndex] == null - || finalCombinedModes[simInfo.slotIndex] != requestedMode) { - return CommandResult.failed(command, - "Legacy combined mode verification failed."); - } - if (originalRadios == null ? finalRadios != null - : !originalRadios.equals(finalRadios)) { - return CommandResult.failed(command, - "Original Airplane mode radio configuration was not restored."); - } - + return CommandResult.completed(command, 0, - "Legacy network mode applied and verified.", ""); - } - - private String shellEscape(String value) { - return "'" + value.replace("'", "'\\''") + "'"; + "Legacy network mode applied successfully via Java payload.", ""); } } From e71a053bcf408469fabf38101655f08d4f751bd7 Mon Sep 17 00:00:00 2001 From: DK <117024745+Dhangofa@users.noreply.github.com> Date: Wed, 19 Aug 2026 00:15:21 +0530 Subject: [PATCH 07/14] Add standalone Java payload for legacy(Android 7 to 11) network switching - Created a headless Java class with a main method designed to run natively via app_process outside the standard app lifecycle. - Implemented Java Reflection to securely hook into the hidden android.os.ServiceManager and retrieve the phone service binder. - Added logic to invoke ITelephony.setPreferredNetworkType(subId, mode) directly, bypassing standard Android permission restrictions. - Added @Keep annotations to the class and main method to ensure R8/ProGuard does not strip or obfuscate the payload during release builds. --- .../telephony/NetworkModePayload.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 app/src/main/java/com/dhangofa/networktoggle/telephony/NetworkModePayload.java diff --git a/app/src/main/java/com/dhangofa/networktoggle/telephony/NetworkModePayload.java b/app/src/main/java/com/dhangofa/networktoggle/telephony/NetworkModePayload.java new file mode 100644 index 0000000..397337d --- /dev/null +++ b/app/src/main/java/com/dhangofa/networktoggle/telephony/NetworkModePayload.java @@ -0,0 +1,55 @@ +package com.dhangofa.networktoggle.telephony; + +import android.annotation.SuppressLint; +import android.os.IBinder; +import androidx.annotation.Keep; +import java.lang.reflect.Method; + +@Keep +public class NetworkModePayload { + @Keep + public static void main(String[] args) { + try { + if (args.length < 2) { + System.err.println("Usage: "); + System.exit(1); + } + int subId = Integer.parseInt(args[0]); + int networkMode = Integer.parseInt(args[1]); + + @SuppressLint("PrivateApi") + Class serviceManagerClass = Class.forName("android.os.ServiceManager"); + Method getServiceMethod = serviceManagerClass.getDeclaredMethod("getService", String.class); + IBinder binder = (IBinder) getServiceMethod.invoke(null, "phone"); + + if (binder == null) { + System.err.println("Failed to get 'phone' service binder."); + System.exit(1); + } + + @SuppressLint("PrivateApi") + Class iTelephonyStubClass = Class.forName("com.android.internal.telephony.ITelephony$Stub"); + Method asInterfaceMethod = iTelephonyStubClass.getDeclaredMethod("asInterface", IBinder.class); + Object iTelephony = asInterfaceMethod.invoke(null, binder); + + if (iTelephony == null) { + System.err.println("Failed to get ITelephony interface."); + System.exit(1); + } + + Method setPrefNetworkTypeMethod = iTelephony.getClass().getMethod("setPreferredNetworkType", int.class, int.class); + Object result = setPrefNetworkTypeMethod.invoke(iTelephony, subId, networkMode); + + if (result instanceof Boolean && !((Boolean) result)) { + System.err.println("ITelephony returned false."); + System.exit(1); + } else { + System.out.println("Success"); + System.exit(0); + } + } catch (Exception e) { + e.printStackTrace(System.err); + System.exit(1); + } + } +} From 25bfa6af14ace95616a6fe6eb9a9619ed35cc6ba Mon Sep 17 00:00:00 2001 From: DK <117024745+Dhangofa@users.noreply.github.com> Date: Wed, 19 Aug 2026 00:18:56 +0530 Subject: [PATCH 08/14] Remove unused color resources from light theme - Removed the unused segmented_selected color hex value. - Removed the unused segmented_text_selected color hex value. - Removed the unused accent_green color hex value. - Removed the unused cycle_badge_bg color hex value to resolve Android Lint warnings and clean up the resource file. --- app/src/main/res/values/colors.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 9352d40..fd30123 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -27,12 +27,9 @@ #CAC4D0 #F3EDF7 - #EADDFF - #21005D #49454F - #198754 #D96509 #C2185B @@ -45,7 +42,6 @@ #FFF8F3 #FFE0C2 - #F3EDF7 #7A3600 From dc608fa4bcb638fe5fbb26a6e50737f80aa39d8d Mon Sep 17 00:00:00 2001 From: DK <117024745+Dhangofa@users.noreply.github.com> Date: Wed, 19 Aug 2026 00:20:20 +0530 Subject: [PATCH 09/14] Remove unused color resources from dark theme - Removed the unused segmented_selected color hex value. - Removed the unused segmented_text_selected color hex value. - Removed the unused accent_green color hex value. - Removed the unused cycle_badge_bg color hex value to maintain parity with the light theme cleanup and resolve Lint warnings. --- app/src/main/res/values-night/colors.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 229b26e..907d381 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -27,12 +27,9 @@ #49454F #1B1A20 - #36323F - #EADDFF #938F96 - #61D48A #FFB782 #F48FB1 @@ -45,7 +42,6 @@ #1A130F #3D210F - #1B1A20 #FFB782 From 82b9b2fab2aacafbef46596e227cd7f079385ca7 Mon Sep 17 00:00:00 2001 From: DK <117024745+Dhangofa@users.noreply.github.com> Date: Wed, 19 Aug 2026 00:21:39 +0530 Subject: [PATCH 10/14] Delete obsolete segmented text color state list Deleted the unused XML color state list selector (color_segmented_text.xml) identified during the Android Lint analysis to reduce APK size. --- app/src/main/res/color/color_segmented_text.xml | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 app/src/main/res/color/color_segmented_text.xml diff --git a/app/src/main/res/color/color_segmented_text.xml b/app/src/main/res/color/color_segmented_text.xml deleted file mode 100644 index 4d24810..0000000 --- a/app/src/main/res/color/color_segmented_text.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - From 1505c7b35b72ded19d9ceb0fbb3e7b5aac92b719 Mon Sep 17 00:00:00 2001 From: DK <117024745+Dhangofa@users.noreply.github.com> Date: Wed, 19 Aug 2026 00:22:52 +0530 Subject: [PATCH 11/14] Delete unused menu book vector icon - Removed the ic_menu_book.xml Material Design vector drawable that was no longer referenced by any UI layouts. --- app/src/main/res/drawable/ic_menu_book.xml | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 app/src/main/res/drawable/ic_menu_book.xml diff --git a/app/src/main/res/drawable/ic_menu_book.xml b/app/src/main/res/drawable/ic_menu_book.xml deleted file mode 100644 index 1564a49..0000000 --- a/app/src/main/res/drawable/ic_menu_book.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - From bbcd2aaec9b7b45a64804d21bfc5a52d1795f40a Mon Sep 17 00:00:00 2001 From: DK <117024745+Dhangofa@users.noreply.github.com> Date: Wed, 19 Aug 2026 00:24:48 +0530 Subject: [PATCH 12/14] Delete unused section outline background drawable - Removed the obsolete section_outline_bg.xml shape drawable to eliminate dead code and shrink the resource bundle. --- app/src/main/res/drawable/section_outline_bg.xml | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 app/src/main/res/drawable/section_outline_bg.xml diff --git a/app/src/main/res/drawable/section_outline_bg.xml b/app/src/main/res/drawable/section_outline_bg.xml deleted file mode 100644 index 98e1e46..0000000 --- a/app/src/main/res/drawable/section_outline_bg.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - From c8fb8a7d105a51b407cb4fa5f562e080d3bcbd81 Mon Sep 17 00:00:00 2001 From: DK <117024745+Dhangofa@users.noreply.github.com> Date: Wed, 19 Aug 2026 00:25:47 +0530 Subject: [PATCH 13/14] Delete obsolete segmented tab selector - Deleted the unused selector_segmented_tab.xml background state selector based on the Lint dead-code analysis. --- .../res/drawable/selector_segmented_tab.xml | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 app/src/main/res/drawable/selector_segmented_tab.xml diff --git a/app/src/main/res/drawable/selector_segmented_tab.xml b/app/src/main/res/drawable/selector_segmented_tab.xml deleted file mode 100644 index 5dfadb5..0000000 --- a/app/src/main/res/drawable/selector_segmented_tab.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - From ad14ba738edd6b4b2708110bb0642bea99a2e0ba Mon Sep 17 00:00:00 2001 From: DK <117024745+Dhangofa@users.noreply.github.com> Date: Wed, 19 Aug 2026 00:26:46 +0530 Subject: [PATCH 14/14] Delete obsolete rectangular segmented tab selector - Deleted the unused rectangular variant of the segmented tab background selector (selector_segmented_tab_rect.xml) to keep the codebase clean. --- .../res/drawable/selector_segmented_tab_rect.xml | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 app/src/main/res/drawable/selector_segmented_tab_rect.xml diff --git a/app/src/main/res/drawable/selector_segmented_tab_rect.xml b/app/src/main/res/drawable/selector_segmented_tab_rect.xml deleted file mode 100644 index 66d1e06..0000000 --- a/app/src/main/res/drawable/selector_segmented_tab_rect.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - -