Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,33 +1,37 @@
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; }
public String getDisplayName() { return displayName; }
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()) {
Expand Down Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -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.", "");
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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: <subId> <networkMode>");
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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand All @@ -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) {
Expand All @@ -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<SubscriptionInfo> 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()) {
Expand All @@ -118,7 +119,6 @@ public SimInfo resolveTargetSimInfo(ExecutionMode executionMode) {
}
}
}

// 4. Final Validation
if (isValidSubId(targetSubId) && isValidSlotIndex(targetSlotIndex)) {
if (targetSim.isAuto()) appPreferences.setAutoSimError(false);
Expand All @@ -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);
Expand Down
10 changes: 0 additions & 10 deletions app/src/main/res/color/color_segmented_text.xml

This file was deleted.

14 changes: 0 additions & 14 deletions app/src/main/res/drawable/ic_menu_book.xml

This file was deleted.

11 changes: 0 additions & 11 deletions app/src/main/res/drawable/section_outline_bg.xml

This file was deleted.

17 changes: 0 additions & 17 deletions app/src/main/res/drawable/selector_segmented_tab.xml

This file was deleted.

15 changes: 0 additions & 15 deletions app/src/main/res/drawable/selector_segmented_tab_rect.xml

This file was deleted.

Loading