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: 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..30c6be6 --- /dev/null +++ b/app/src/main/java/com/dhangofa/networktoggle/telephony/LegacyNetworkModeController.java @@ -0,0 +1,49 @@ +package com.dhangofa.networktoggle.telephony; + +import android.content.Context; +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 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."); + } + + 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."); + } + + 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 CommandResult.completed(command, 0, + "Legacy network mode applied successfully via Java payload.", ""); + } +} 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) { 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); + } + } +} 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); 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 @@ - - - - - - - - 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 @@ - - - - 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 @@ - - - - - - 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 @@ - - - - - - - - - - - - - - - - - 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 @@ - - - - - - - - - - - - - - - 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 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