diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c27905b..b9b8ed6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -10,7 +10,7 @@ android { applicationId = "com.dhangofa.networktoggle" minSdk = 24 targetSdk = 36 - versionCode = 2 + versionCode = (project.findProperty("versionCode") as String?)?.toIntOrNull() ?: 2 versionName = "1.0" } // Suggested by IzzyOnDroid diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 67b7c78..e4fe6a3 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,2 +1,6 @@ # Keep the Quick Settings TileService intact during R8/Proguard minification -keep class com.dhangofa.networktoggle.NetworkTileService { *; } +-keep class rikka.shizuku.** { *; } +-keep class moe.shizuku.** { *; } +-dontwarn rikka.shizuku.** +-dontwarn moe.shizuku.** diff --git a/app/src/main/java/com/dhangofa/networktoggle/MainActivity.java b/app/src/main/java/com/dhangofa/networktoggle/MainActivity.java index 62f1a79..3fc5a79 100644 --- a/app/src/main/java/com/dhangofa/networktoggle/MainActivity.java +++ b/app/src/main/java/com/dhangofa/networktoggle/MainActivity.java @@ -1,3 +1,13 @@ +/* +Behavior: +- Fresh install: no option selected. +- Root selected: root permission checked. +- Shizuku selected: Shizuku permission checked/requested only then. +- Saved Root: root rechecked on open. +- Saved Shizuku: status checked without auto-popup. +- Shizuku callbacks update UI only when Shizuku mode is selected. +*/ + package com.dhangofa.networktoggle; import android.app.Activity; @@ -7,41 +17,68 @@ import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.TextView; + import rikka.shizuku.Shizuku; public class MainActivity extends Activity { private static final String PREFS_NAME = "NetTogglePrefs"; private static final String EXEC_MODE_KEY = "exec_mode"; + + private static final int MODE_NONE = 0; + private static final int MODE_ROOT = 1; + private static final int MODE_SHIZUKU = 2; private RadioGroup radioGroup; private RadioButton radioRoot; private RadioButton radioShizuku; private TextView statusText; private SharedPreferences prefs; - - // 1. Wait for Shizuku Binder to be injected, then check permissions + + private volatile boolean activityDestroyed = false; + private Thread rootCheckThread; + private Process rootCheckProcess; + + // 1. First check Shizuku mode selected then wait for Shizuku Binder to be injected, then check permissions private final Shizuku.OnBinderReceivedListener binderReceivedListener = () -> { - runOnUiThread(this::checkShizukuPermission); - }; + runOnUiThread(() -> { + if (!activityDestroyed + && prefs != null + && prefs.getInt(EXEC_MODE_KEY, MODE_NONE) == MODE_SHIZUKU) { + checkShizukuPermission(false); + } + }); + }; // 2. Handle if Shizuku suddenly dies in the background private final Shizuku.OnBinderDeadListener binderDeadListener = () -> { - runOnUiThread(() -> { - statusText.setText("Shizuku is not running."); - statusText.setTextColor(0xFFFF5555); // Red - }); - }; + runOnUiThread(() -> { + if (!activityDestroyed + && prefs != null + && prefs.getInt(EXEC_MODE_KEY, MODE_NONE) == MODE_SHIZUKU) { + statusText.setText("Shizuku is not running."); + statusText.setTextColor(0xFFFF5555); + } + }); + }; + // 3. React instantly when the user taps "Allow" on the Shizuku Popup private final Shizuku.OnRequestPermissionResultListener permissionResultListener = (requestCode, grantResult) -> { - runOnUiThread(this::checkShizukuPermission); - }; + runOnUiThread(() -> { + if (!activityDestroyed + && prefs != null + && prefs.getInt(EXEC_MODE_KEY, MODE_NONE) == MODE_SHIZUKU) { + checkShizukuPermission(false); + } + }); + }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); + activityDestroyed = false; + setContentView(R.layout.activity_main); prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); @@ -55,49 +92,133 @@ protected void onCreate(Bundle savedInstanceState) { Shizuku.addBinderDeadListener(binderDeadListener); Shizuku.addRequestPermissionResultListener(permissionResultListener); - // Load saved mode (Default: Root = 1, Shizuku = 2) - int savedMode = prefs.getInt(EXEC_MODE_KEY, 1); - if (savedMode == 2) { + // Load saved mode (Default =0, Root = 1, Shizuku = 2) + int savedMode = prefs.getInt(EXEC_MODE_KEY, MODE_NONE); + if (savedMode == MODE_ROOT) { + radioRoot.setChecked(true); + checkRootPermission(); + } else if (savedMode == MODE_SHIZUKU) { radioShizuku.setChecked(true); - checkShizukuPermission(); // Try immediately + checkShizukuPermission(false); } else { - radioRoot.setChecked(true); + radioGroup.clearCheck(); + statusText.setText("Select Root or Shizuku mode."); + statusText.setTextColor(0xFFFFB300); } radioGroup.setOnCheckedChangeListener((group, checkedId) -> { if (checkedId == R.id.radioRoot) { - prefs.edit().putInt(EXEC_MODE_KEY, 1).apply(); - statusText.setText(""); + prefs.edit().putInt(EXEC_MODE_KEY, MODE_ROOT).apply(); + checkRootPermission(); } else if (checkedId == R.id.radioShizuku) { - prefs.edit().putInt(EXEC_MODE_KEY, 2).apply(); - checkShizukuPermission(); + prefs.edit().putInt(EXEC_MODE_KEY, MODE_SHIZUKU).apply(); + checkShizukuPermission(true); } }); } - @Override - protected void onDestroy() { - super.onDestroy(); - // Prevent memory leaks by destroying the listeners when the app closes - Shizuku.removeBinderReceivedListener(binderReceivedListener); - Shizuku.removeBinderDeadListener(binderDeadListener); - Shizuku.removeRequestPermissionResultListener(permissionResultListener); - } + @Override + protected void onDestroy() { + activityDestroyed = true; + + // Prevent memory leaks by destroying Shizuku listeners when the app closes + Shizuku.removeBinderReceivedListener(binderReceivedListener); + Shizuku.removeBinderDeadListener(binderDeadListener); + Shizuku.removeRequestPermissionResultListener(permissionResultListener); + + // Stop any running root permission check process + if (rootCheckProcess != null) { + rootCheckProcess.destroy(); + rootCheckProcess = null; + } + + // Interrupt root check thread if it is still active + if (rootCheckThread != null && rootCheckThread.isAlive()) { + rootCheckThread.interrupt(); + rootCheckThread = null; + } + + super.onDestroy(); + } - private void checkShizukuPermission() { - if (!Shizuku.pingBinder()) { - statusText.setText("Waiting for Shizuku..."); - statusText.setTextColor(0xFFFF5555); // Red - return; - } + private void checkRootPermission() { + statusText.setText("Checking root permission..."); + statusText.setTextColor(0xFFFFB300); + + rootCheckThread = new Thread(() -> { + boolean granted = false; + Process process = null; + + try { + process = Runtime.getRuntime().exec(new String[]{"su", "-c", "id"}); + rootCheckProcess = process; + + int exitCode = process.waitFor(); + granted = exitCode == 0; + } catch (Exception ignored) { + granted = false; + } finally { + if (process != null) { + process.destroy(); + } + + if (rootCheckProcess == process) { + rootCheckProcess = null; + } + } + + boolean finalGranted = granted; + + runOnUiThread(() -> { + if (activityDestroyed) { + return; + } + + if (prefs == null || prefs.getInt(EXEC_MODE_KEY, MODE_NONE) != MODE_ROOT) { + return; + } + + if (finalGranted) { + statusText.setText("Root mode active & authorized!"); + statusText.setTextColor(0xFF1B873F); + } else { + statusText.setText("Root permission denied or unavailable."); + statusText.setTextColor(0xFFFF5555); + } + }); + }); + + rootCheckThread.start(); + } + + private void checkShizukuPermission(boolean requestIfNeeded) { + if (activityDestroyed) { + return; + } + try { + if (!Shizuku.pingBinder()) { + statusText.setText("Shizuku is not running."); + statusText.setTextColor(0xFFFF5555); + return; + } - if (Shizuku.checkSelfPermission() != PackageManager.PERMISSION_GRANTED) { - statusText.setText("Requesting Shizuku permission..."); - statusText.setTextColor(0xFFFFB300); // Yellow - Shizuku.requestPermission(0); // This line triggers the Auto-Popup - } else { - statusText.setText("Shizuku mode active & authorized!"); - statusText.setTextColor(0xFF1B873F); // Green - } - } + if (Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) { + statusText.setText("Shizuku mode active & authorized!"); + statusText.setTextColor(0xFF1B873F); + return; + } + + statusText.setText("Shizuku permission not granted."); + statusText.setTextColor(0xFFFFB300); + + if (requestIfNeeded) { + statusText.setText("Requesting Shizuku permission..."); + statusText.setTextColor(0xFFFFB300); + Shizuku.requestPermission(0); + } + } catch (Exception e) { + statusText.setText("Shizuku check failed."); + statusText.setTextColor(0xFFFF5555); + } + } } diff --git a/app/src/main/java/com/dhangofa/networktoggle/NetworkTileService.java b/app/src/main/java/com/dhangofa/networktoggle/NetworkTileService.java index a365b55..c96f9db 100644 --- a/app/src/main/java/com/dhangofa/networktoggle/NetworkTileService.java +++ b/app/src/main/java/com/dhangofa/networktoggle/NetworkTileService.java @@ -9,8 +9,17 @@ import android.graphics.drawable.Icon; import android.service.quicksettings.Tile; import android.service.quicksettings.TileService; -import java.io.DataOutputStream; +import android.os.Handler; +import android.os.Looper; +import java.util.concurrent.atomic.AtomicBoolean; import java.lang.reflect.Method; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import rikka.shizuku.Shizuku; public class NetworkTileService extends TileService { @@ -18,41 +27,178 @@ public class NetworkTileService extends TileService { private static final String PREFS_NAME = "NetTogglePrefs"; private static final String STATE_KEY = "net_state"; private static final String EXEC_MODE_KEY = "exec_mode"; + + private static final int MODE_NONE = 0; + private static final int MODE_ROOT = 1; + private static final int MODE_SHIZUKU = 2; + + private static final int STATE_UNKNOWN = 0; + private static final int STATE_4G_ONLY = 1; + private static final int STATE_5G_ONLY = 2; + private static final int STATE_PREF_5G = 3; + private static final int STATE_PREF_4G = 4; - private static final String BIN_4G_ONLY = "1000000000000"; - private static final String BIN_5G_ONLY = "10000000000000000000"; - private static final String BIN_PREF_5G = "11011111101111111111"; - private static final String BIN_PREF_4G = "1001101001110000111"; + private static final String BIN_4G_ONLY = "1000000000000"; // Legacy Id 11, bitmask 4096 + private static final String BIN_5G_ONLY = "10000000000000000000"; // Legacy Id 23, bitmask 524288 + private static final String BIN_PREF_5G = "11011111101111111111"; // Legacy Id 33, bitmask 916479 + private static final String BIN_PREF_4G = "1001101001110000111"; // Legacy Id 9, bitmask 316295 + + private static final int LEGACY_4G_ONLY = 11; + private static final int LEGACY_5G_ONLY = 23; + private static final int LEGACY_PREF_5G = 33; + private static final int LEGACY_PREF_4G = 9; + + private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor(); + private static final AtomicBoolean IS_SWITCHING = new AtomicBoolean(false); + + private static final Pattern NUMBER_PATTERN = Pattern.compile("\\d+"); + + private final Handler mainHandler = new Handler(Looper.getMainLooper()); + + private static Method SHIZUKU_NEW_PROCESS_METHOD; + + private static Icon ICON_4G; + private static Icon ICON_5G; + private static Icon ICON_P5G; + private static Icon ICON_P4G; + private static Icon ICON_UNKNOWN; - @Override - public void onStartListening() { - super.onStartListening(); - SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); - updateTileUI(prefs.getInt(STATE_KEY, 1)); - } + @Override + public void onStartListening() { + super.onStartListening(); - @Override - public void onClick() { - super.onClick(); - SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); - int currentState = prefs.getInt(STATE_KEY, 1); + SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); + int cachedState = prefs.getInt(STATE_KEY, STATE_UNKNOWN); - int nextState = (currentState % 4) + 1; - String targetBinary = BIN_4G_ONLY; + updateTileUI(cachedState); - switch (nextState) { - case 1: targetBinary = BIN_4G_ONLY; break; - case 2: targetBinary = BIN_5G_ONLY; break; - case 3: targetBinary = BIN_PREF_5G; break; - case 4: targetBinary = BIN_PREF_4G; break; - } + int execMode = prefs.getInt(EXEC_MODE_KEY, MODE_NONE); + if (execMode == MODE_NONE) { + return; + } - applyNetworkMode(targetBinary); + // Lightweight behavior: + // Only read real system mode if we do not have a cached state yet. + if (cachedState != STATE_UNKNOWN) { + return; + } - prefs.edit().putInt(STATE_KEY, nextState).apply(); - updateTileUI(nextState); - } + EXECUTOR.execute(() -> { + int realState = readCurrentNetworkState(); + + mainHandler.post(() -> { + if (realState != STATE_UNKNOWN) { + prefs.edit().putInt(STATE_KEY, realState).apply(); + updateTileUI(realState); + } else { + updateTileUI(cachedState); + } + }); + }); + } + + @Override + public void onClick() { + super.onClick(); + + if (!IS_SWITCHING.compareAndSet(false, true)) { + updateTileSwitchingUI(); + return; + } + + SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); + + int execMode = prefs.getInt(EXEC_MODE_KEY, MODE_NONE); + if (execMode == MODE_NONE) { + IS_SWITCHING.set(false); + updateTileUI(STATE_UNKNOWN); + return; + } + + int currentState = prefs.getInt(STATE_KEY, STATE_UNKNOWN); + + int nextState = getNextState(currentState); + String targetBinary = getBinaryForState(nextState); + + updateTileSwitchingUI(); + + EXECUTOR.execute(() -> { + boolean success = applyNetworkMode(targetBinary); + + mainHandler.post(() -> { + try { + if (success) { + prefs.edit().putInt(STATE_KEY, nextState).apply(); + updateTileUI(nextState); + } else { + updateTileUI(currentState); + } + } finally { + IS_SWITCHING.set(false); + } + }); + }); + } + + private int getNextState(int currentState) { + switch (currentState) { + case STATE_4G_ONLY: + return STATE_5G_ONLY; + + case STATE_5G_ONLY: + return STATE_PREF_5G; + + case STATE_PREF_5G: + return STATE_PREF_4G; + + case STATE_PREF_4G: + case STATE_UNKNOWN: + default: + return STATE_4G_ONLY; + } + } + + private String getBinaryForState(int state) { + switch (state) { + case STATE_5G_ONLY: + return BIN_5G_ONLY; + + case STATE_PREF_5G: + return BIN_PREF_5G; + + case STATE_PREF_4G: + return BIN_PREF_4G; + + case STATE_4G_ONLY: + default: + return BIN_4G_ONLY; + } + } + + private static Method getShizukuNewProcessMethod() throws NoSuchMethodException { + if (SHIZUKU_NEW_PROCESS_METHOD == null) { + SHIZUKU_NEW_PROCESS_METHOD = Shizuku.class.getDeclaredMethod( + "newProcess", + String[].class, + String[].class, + String.class + ); + SHIZUKU_NEW_PROCESS_METHOD.setAccessible(true); + } + + return SHIZUKU_NEW_PROCESS_METHOD; + } + + private static class CommandResult { + final int exitCode; + final String stdout; + CommandResult(int exitCode, String stdout) { + this.exitCode = exitCode; + this.stdout = stdout; + } + } + private Icon createTextOnlyIcon(String text) { int size = 256; Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); @@ -76,67 +222,318 @@ private Icon createTextOnlyIcon(String text) { return Icon.createWithBitmap(bitmap); } + + private Icon getCachedIcon(String text) { + switch (text) { + case "4G": + if (ICON_4G == null) { + ICON_4G = createTextOnlyIcon("4G"); + } + return ICON_4G; + + case "5G": + if (ICON_5G == null) { + ICON_5G = createTextOnlyIcon("5G"); + } + return ICON_5G; + + case "P5G": + if (ICON_P5G == null) { + ICON_P5G = createTextOnlyIcon("P5G"); + } + return ICON_P5G; + + case "P4G": + if (ICON_P4G == null) { + ICON_P4G = createTextOnlyIcon("P4G"); + } + return ICON_P4G; + + default: + if (ICON_UNKNOWN == null) { + ICON_UNKNOWN = createTextOnlyIcon("?"); + } + return ICON_UNKNOWN; + } + } + + private void updateTileSwitchingUI() { + Tile tile = getQsTile(); + if (tile == null) return; + + tile.setState(Tile.STATE_INACTIVE); + tile.setLabel("Switching..."); + tile.setIcon(getCachedIcon("?")); + tile.updateTile(); + } private void updateTileUI(int state) { - Tile tile = getQsTile(); - if (tile == null) return; - tile.setState(Tile.STATE_ACTIVE); - - switch (state) { - case 1: - tile.setLabel("4G Only"); - tile.setIcon(createTextOnlyIcon("4G")); - break; - case 2: - tile.setLabel("5G Only"); - tile.setIcon(createTextOnlyIcon("5G")); - break; - case 3: - tile.setLabel("Pref 5G"); - tile.setIcon(createTextOnlyIcon("P5G")); - break; - case 4: - tile.setLabel("Pref 4G"); - tile.setIcon(createTextOnlyIcon("P4G")); - break; - } - tile.updateTile(); - } + Tile tile = getQsTile(); + if (tile == null) return; - private void applyNetworkMode(String binaryString) { - SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); - int execMode = prefs.getInt(EXEC_MODE_KEY, 1); // 1 = Root, 2 = Shizuku - String command = "cmd phone set-allowed-network-types-for-users -s 0 " + binaryString; - - if (execMode == 2) { - // Shizuku Execution Method via Reflection - try { - if (Shizuku.pingBinder() && Shizuku.checkSelfPermission() == android.content.pm.PackageManager.PERMISSION_GRANTED) { - - // Uses Reflection to bypass the private access restriction on Shizuku.newProcess - Method newProcessMethod = Shizuku.class.getDeclaredMethod("newProcess", String[].class, String[].class, String.class); - newProcessMethod.setAccessible(true); - - Process process = (Process) newProcessMethod.invoke(null, new String[]{"sh", "-c", command}, null, null); - if (process != null) { - process.waitFor(); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } else { - // Standard Root Execution Method - try { - Process process = Runtime.getRuntime().exec("su"); - DataOutputStream os = new DataOutputStream(process.getOutputStream()); - os.writeBytes(command + "\n"); - os.writeBytes("exit\n"); - os.flush(); - process.waitFor(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } + switch (state) { + case STATE_4G_ONLY: + tile.setState(Tile.STATE_ACTIVE); + tile.setLabel("4G Only"); + tile.setIcon(getCachedIcon("4G")); + break; + + case STATE_5G_ONLY: + tile.setState(Tile.STATE_ACTIVE); + tile.setLabel("5G Only"); + tile.setIcon(getCachedIcon("5G")); + break; + + case STATE_PREF_5G: + tile.setState(Tile.STATE_ACTIVE); + tile.setLabel("Pref 5G"); + tile.setIcon(getCachedIcon("P5G")); + break; + + case STATE_PREF_4G: + tile.setState(Tile.STATE_ACTIVE); + tile.setLabel("Pref 4G"); + tile.setIcon(getCachedIcon("P4G")); + break; + + case STATE_UNKNOWN: + default: + SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); + int execMode = prefs.getInt(EXEC_MODE_KEY, MODE_NONE); + + if (execMode == MODE_NONE) { + tile.setState(Tile.STATE_UNAVAILABLE); + tile.setLabel("Setup Required"); + } else { + tile.setState(Tile.STATE_INACTIVE); + tile.setLabel("Tap to Set 4G"); + } + + tile.setIcon(getCachedIcon("?")); + break; + } + + tile.updateTile(); + } + + private int readCurrentNetworkState() { + SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); + int execMode = prefs.getInt(EXEC_MODE_KEY, MODE_NONE); + + if (execMode == MODE_NONE) { + return STATE_UNKNOWN; + } + + String command = + "data_sim=$(settings get global multi_sim_data_call); " + + "[ \"$data_sim\" -gt 0 ] 2>/dev/null || exit 1; " + + "settings get global preferred_network_mode${data_sim}"; + + CommandResult result; + + if (execMode == MODE_SHIZUKU) { + result = runCommandForResultWithShizuku(command); + } else if (execMode == MODE_ROOT) { + result = runCommandForResultWithRoot(command); + } else { + return STATE_UNKNOWN; + } + + if (result.exitCode != 0) { + return STATE_UNKNOWN; + } + + return mapLegacyNetworkModeToState(result.stdout); + } + + private CommandResult runCommandForResultWithRoot(String command) { + Process process = null; + + try { + process = Runtime.getRuntime().exec(new String[]{"su", "-c", command}); + + int exitCode = process.waitFor(); + String stdout = readStream(process.getInputStream()); + + return new CommandResult(exitCode, stdout); + } catch (Exception e) { + return new CommandResult(-1, ""); + } finally { + if (process != null) { + process.destroy(); + } + } + } + + private CommandResult runCommandForResultWithShizuku(String command) { + Process process = null; + + try { + if (!Shizuku.pingBinder()) { + return new CommandResult(-1, ""); + } + + if (Shizuku.checkSelfPermission() != android.content.pm.PackageManager.PERMISSION_GRANTED) { + return new CommandResult(-1, ""); + } + + process = (Process) getShizukuNewProcessMethod().invoke( + null, + new String[]{"sh", "-c", command}, + null, + null + ); + + if (process == null) { + return new CommandResult(-1, ""); + } + + int exitCode = process.waitFor(); + String stdout = readStream(process.getInputStream()); + + return new CommandResult(exitCode, stdout); + } catch (Exception e) { + return new CommandResult(-1, ""); + } finally { + if (process != null) { + process.destroy(); + } + } + } + + private String readStream(InputStream inputStream) { + StringBuilder builder = new StringBuilder(); + + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + String line; + + while ((line = reader.readLine()) != null) { + builder.append(line).append('\n'); + } + } catch (Exception ignored) { + } + + return builder.toString().trim(); + } + + private Integer extractFirstInt(String text) { + try { + if (text == null || text.trim().isEmpty() || text.trim().equalsIgnoreCase("null")) { + return null; + } + + Matcher matcher = NUMBER_PATTERN.matcher(text); + + if (matcher.find()) { + return Integer.parseInt(matcher.group()); + } + + return null; + } catch (Exception e) { + return null; + } + } + + private int mapLegacyNetworkModeToState(String output) { + Integer legacyMode = extractFirstInt(output); + + if (legacyMode == null) { + return STATE_UNKNOWN; + } + + switch (legacyMode) { + case LEGACY_4G_ONLY: + return STATE_4G_ONLY; + + case LEGACY_5G_ONLY: + return STATE_5G_ONLY; + + case LEGACY_PREF_5G: + return STATE_PREF_5G; + + case LEGACY_PREF_4G: + return STATE_PREF_4G; + + default: + return STATE_UNKNOWN; + } + } + + + private boolean applyNetworkMode(String binaryString) { + SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); + int execMode = prefs.getInt(EXEC_MODE_KEY, MODE_NONE); + + if (execMode == MODE_NONE) { + return false; + } + + String command = + "data_sim=$(settings get global multi_sim_data_call); " + + "[ \"$data_sim\" -gt 0 ] 2>/dev/null || exit 1; " + + "phone_index=$((data_sim - 1)); " + + "cmd phone set-allowed-network-types-for-users -s \"$phone_index\" " + binaryString; + + if (execMode == MODE_SHIZUKU) { + return runCommandWithShizuku(command); + } else if (execMode == MODE_ROOT) { + return runCommandWithRoot(command); + } + + return false; + } + + private boolean runCommandWithRoot(String command) { + Process process = null; + + try { + process = Runtime.getRuntime().exec(new String[]{"su", "-c", command}); + int exitCode = process.waitFor(); + return exitCode == 0; + } catch (Exception e) { + e.printStackTrace(); + return false; + } finally { + if (process != null) { + process.destroy(); + } + } + } + + private boolean runCommandWithShizuku(String command) { + Process process = null; + + try { + if (!Shizuku.pingBinder()) { + return false; + } + + if (Shizuku.checkSelfPermission() != android.content.pm.PackageManager.PERMISSION_GRANTED) { + return false; + } + + process = (Process) getShizukuNewProcessMethod().invoke( + null, + new String[]{"sh", "-c", command}, + null, + null + ); + + if (process == null) { + return false; + } + + int exitCode = process.waitFor(); + return exitCode == 0; + + } catch (Exception e) { + e.printStackTrace(); + return false; + }finally { + if (process != null) { + process.destroy(); + } + } + } }