diff --git a/app/src/main/java/com/dhangofa/networktoggle/NetworkTileService.java b/app/src/main/java/com/dhangofa/networktoggle/NetworkTileService.java index 72e1e50..6ee741a 100644 --- a/app/src/main/java/com/dhangofa/networktoggle/NetworkTileService.java +++ b/app/src/main/java/com/dhangofa/networktoggle/NetworkTileService.java @@ -1,6 +1,5 @@ package com.dhangofa.networktoggle; -import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; @@ -15,30 +14,26 @@ import android.telephony.SubscriptionManager; import android.widget.Toast; +import com.dhangofa.networktoggle.command.CommandExecutor; +import com.dhangofa.networktoggle.command.CommandExecutorFactory; import com.dhangofa.networktoggle.config.AppPreferences; +import com.dhangofa.networktoggle.model.CommandResult; import com.dhangofa.networktoggle.model.ExecutionMode; import com.dhangofa.networktoggle.model.NetworkMode; import com.dhangofa.networktoggle.model.TargetSim; -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.lang.reflect.Method; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; -import rikka.shizuku.Shizuku; - public class NetworkTileService extends TileService { private static final int INVALID_SLOT_INDEX = -1; private static final int INVALID_SUB_ID = -1; 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 static Method shizukuNewProcessMethod; private static Icon icon4g; private static Icon icon5g; @@ -115,24 +110,6 @@ public void onClick() { }); } - private static Method getShizukuNewProcessMethod() throws NoSuchMethodException { - if (shizukuNewProcessMethod == null) { - shizukuNewProcessMethod = Shizuku.class.getDeclaredMethod( - "newProcess", String[].class, String[].class, String.class); - shizukuNewProcessMethod.setAccessible(true); - } - return shizukuNewProcessMethod; - } - - 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); @@ -217,57 +194,17 @@ private NetworkMode readCurrentNetworkMode() { } CommandResult result = runCommandForResult(executionMode, command); - if (result.exitCode != 0) return NetworkMode.UNKNOWN; - return NetworkMode.fromLegacyMode(extractFirstInt(result.stdout)); - } - - private CommandResult runCommandForResult(ExecutionMode mode, String command) { - if (mode == ExecutionMode.SHIZUKU) return runCommandForResultWithShizuku(command); - if (mode == ExecutionMode.ROOT) return runCommandForResultWithRoot(command); - return new CommandResult(-1, ""); - } - - private CommandResult runCommandForResultWithRoot(String command) { - Process process = null; - try { - process = Runtime.getRuntime().exec(new String[]{"su", "-c", command}); - int exitCode = process.waitFor(); - return new CommandResult(exitCode, readStream(process.getInputStream())); - } 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() - || Shizuku.checkSelfPermission() != 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(); - return new CommandResult(exitCode, readStream(process.getInputStream())); - } catch (Exception e) { - return new CommandResult(-1, ""); - } finally { - if (process != null) process.destroy(); - } - } - - private String readStream(InputStream stream) { - StringBuilder builder = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) { - String line; - while ((line = reader.readLine()) != null) builder.append(line).append('\n'); - } catch (Exception ignored) { - } - return builder.toString().trim(); + if (result.getExitCode() != 0) return NetworkMode.UNKNOWN; + return NetworkMode.fromLegacyMode(extractFirstInt(result.getStdout())); } + + private CommandResult runCommandForResult(ExecutionMode mode, String command) { + CommandExecutor executor = CommandExecutorFactory.forMode(mode); + if (executor == null) { + return CommandResult.failed(command, "No execution mode selected."); + } + return executor.execute(command); + } private Integer extractFirstInt(String text) { try { @@ -332,7 +269,7 @@ private int resolveSlotIndexFromDumpsys(ExecutionMode executionMode, int dataSub + "([^0-9]| )\" | head -n 1 | grep -o -E \"simSlotIndex=[0-9]+\" " + "| cut -d '=' -f 2"; CommandResult result = runCommandForResult(executionMode, command); - Integer slot = result.exitCode == 0 ? extractFirstInt(result.stdout) : null; + Integer slot = result.getExitCode() == 0 ? extractFirstInt(result.getStdout()) : null; return slot != null && isValidSlotIndex(slot) ? slot : INVALID_SLOT_INDEX; } @@ -341,44 +278,18 @@ private int resolveSubIdFromDumpsys(ExecutionMode executionMode, int slotIndex) + "([^0-9]| )\" | head -n 1 | grep -o -E \"\\{id=[0-9]+\" " + "| cut -d '=' -f 2"; CommandResult result = runCommandForResult(executionMode, command); - Integer subId = result.exitCode == 0 ? extractFirstInt(result.stdout) : null; + Integer subId = result.getExitCode() == 0 ? extractFirstInt(result.getStdout()) : null; return subId != null && subId > 0 ? subId : INVALID_SUB_ID; } - private boolean applyNetworkMode(NetworkMode mode, ExecutionMode executionMode) { - int slotIndex = resolveTargetSlotIndex(executionMode); - if (!isValidSlotIndex(slotIndex) || mode.getBinaryMask() == null) return false; - String command = "cmd phone set-allowed-network-types-for-users -s " - + slotIndex + " " + mode.getBinaryMask(); - if (executionMode == ExecutionMode.SHIZUKU) return runCommandWithShizuku(command); - if (executionMode == ExecutionMode.ROOT) return runCommandWithRoot(command); - return false; - } + private boolean applyNetworkMode(NetworkMode mode, ExecutionMode executionMode) { + int slotIndex = resolveTargetSlotIndex(executionMode); + if (!isValidSlotIndex(slotIndex) || mode.getBinaryMask() == null) return false; - private boolean runCommandWithRoot(String command) { - Process process = null; - try { - process = Runtime.getRuntime().exec(new String[]{"su", "-c", command}); - return process.waitFor() == 0; - } catch (Exception e) { - return false; - } finally { - if (process != null) process.destroy(); - } - } + String command = "cmd phone set-allowed-network-types-for-users -s " + + slotIndex + " " + mode.getBinaryMask(); + CommandResult result = runCommandForResult(executionMode, command); + return result.isSuccess(); + } - private boolean runCommandWithShizuku(String command) { - Process process = null; - try { - if (!Shizuku.pingBinder() - || Shizuku.checkSelfPermission() != PackageManager.PERMISSION_GRANTED) return false; - process = (Process) getShizukuNewProcessMethod().invoke( - null, new String[]{"sh", "-c", command}, null, null); - return process != null && process.waitFor() == 0; - } catch (Exception e) { - return false; - } finally { - if (process != null) process.destroy(); - } - } } diff --git a/app/src/main/java/com/dhangofa/networktoggle/command/CommandExecutor.java b/app/src/main/java/com/dhangofa/networktoggle/command/CommandExecutor.java new file mode 100644 index 0000000..66cbeab --- /dev/null +++ b/app/src/main/java/com/dhangofa/networktoggle/command/CommandExecutor.java @@ -0,0 +1,7 @@ +package com.dhangofa.networktoggle.command; + +import com.dhangofa.networktoggle.model.CommandResult; + +public interface CommandExecutor { + CommandResult execute(String command); +} diff --git a/app/src/main/java/com/dhangofa/networktoggle/command/CommandExecutorFactory.java b/app/src/main/java/com/dhangofa/networktoggle/command/CommandExecutorFactory.java new file mode 100644 index 0000000..150a4d3 --- /dev/null +++ b/app/src/main/java/com/dhangofa/networktoggle/command/CommandExecutorFactory.java @@ -0,0 +1,16 @@ +package com.dhangofa.networktoggle.command; + +import com.dhangofa.networktoggle.model.ExecutionMode; + +public final class CommandExecutorFactory { + private static final CommandExecutor ROOT = new RootCommandExecutor(); + private static final CommandExecutor SHIZUKU = new ShizukuCommandExecutor(); + + private CommandExecutorFactory() {} + + public static CommandExecutor forMode(ExecutionMode mode) { + if (mode == ExecutionMode.ROOT) return ROOT; + if (mode == ExecutionMode.SHIZUKU) return SHIZUKU; + return null; + } +} diff --git a/app/src/main/java/com/dhangofa/networktoggle/command/ProcessResultReader.java b/app/src/main/java/com/dhangofa/networktoggle/command/ProcessResultReader.java new file mode 100644 index 0000000..3397b81 --- /dev/null +++ b/app/src/main/java/com/dhangofa/networktoggle/command/ProcessResultReader.java @@ -0,0 +1,18 @@ +package com.dhangofa.networktoggle.command; + +import com.dhangofa.networktoggle.model.CommandResult; + +final class ProcessResultReader { + private ProcessResultReader() {} + + static CommandResult collect(String command, Process process) throws Exception { + StreamCollector stdout = new StreamCollector("NetToggle-stdout", process.getInputStream()); + StreamCollector stderr = new StreamCollector("NetToggle-stderr", process.getErrorStream()); + stdout.start(); + stderr.start(); + int exitCode = process.waitFor(); + stdout.join(); + stderr.join(); + return CommandResult.completed(command, exitCode, stdout.getOutput(), stderr.getOutput()); + } +} diff --git a/app/src/main/java/com/dhangofa/networktoggle/command/RootCommandExecutor.java b/app/src/main/java/com/dhangofa/networktoggle/command/RootCommandExecutor.java new file mode 100644 index 0000000..4d53596 --- /dev/null +++ b/app/src/main/java/com/dhangofa/networktoggle/command/RootCommandExecutor.java @@ -0,0 +1,25 @@ +package com.dhangofa.networktoggle.command; + +import com.dhangofa.networktoggle.model.CommandResult; + +public final class RootCommandExecutor implements CommandExecutor { + @Override + public CommandResult execute(String command) { + Process process = null; + try { + process = Runtime.getRuntime().exec(new String[]{"su", "-c", command}); + return ProcessResultReader.collect(command, process); + } catch (Exception e) { + return CommandResult.failed(command, describe(e)); + } finally { + if (process != null) process.destroy(); + } + } + + private String describe(Exception e) { + String message = e.getMessage(); + return message == null || message.trim().isEmpty() + ? e.getClass().getSimpleName() + : e.getClass().getSimpleName() + ": " + message; + } +} diff --git a/app/src/main/java/com/dhangofa/networktoggle/command/ShizukuCommandExecutor.java b/app/src/main/java/com/dhangofa/networktoggle/command/ShizukuCommandExecutor.java new file mode 100644 index 0000000..bce624a --- /dev/null +++ b/app/src/main/java/com/dhangofa/networktoggle/command/ShizukuCommandExecutor.java @@ -0,0 +1,43 @@ +package com.dhangofa.networktoggle.command; + +import android.content.pm.PackageManager; +import com.dhangofa.networktoggle.model.CommandResult; +import java.lang.reflect.Method; +import rikka.shizuku.Shizuku; + +public final class ShizukuCommandExecutor implements CommandExecutor { + private static Method newProcessMethod; + + @Override + public CommandResult execute(String command) { + Process process = null; + try { + if (!Shizuku.pingBinder()) return CommandResult.failed(command, "Shizuku is not running."); + if (Shizuku.checkSelfPermission() != PackageManager.PERMISSION_GRANTED) { + return CommandResult.failed(command, "Shizuku permission is not granted."); + } + process = (Process) getNewProcessMethod().invoke( + null, new String[]{"sh", "-c", command}, null, null); + if (process == null) return CommandResult.failed(command, "Shizuku did not create a shell process."); + return ProcessResultReader.collect(command, process); + } catch (Exception e) { + Throwable cause = e.getCause() == null ? e : e.getCause(); + String message = cause.getMessage(); + return CommandResult.failed(command, + message == null || message.trim().isEmpty() + ? cause.getClass().getSimpleName() + : cause.getClass().getSimpleName() + ": " + message); + } finally { + if (process != null) process.destroy(); + } + } + + private static synchronized Method getNewProcessMethod() throws NoSuchMethodException { + if (newProcessMethod == null) { + newProcessMethod = Shizuku.class.getDeclaredMethod( + "newProcess", String[].class, String[].class, String.class); + newProcessMethod.setAccessible(true); + } + return newProcessMethod; + } +} diff --git a/app/src/main/java/com/dhangofa/networktoggle/command/StreamCollector.java b/app/src/main/java/com/dhangofa/networktoggle/command/StreamCollector.java new file mode 100644 index 0000000..efaca66 --- /dev/null +++ b/app/src/main/java/com/dhangofa/networktoggle/command/StreamCollector.java @@ -0,0 +1,31 @@ +package com.dhangofa.networktoggle.command; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; + +final class StreamCollector extends Thread { + private final InputStream inputStream; + private final StringBuilder output = new StringBuilder(); + + StreamCollector(String name, InputStream inputStream) { + super(name); + this.inputStream = inputStream; + setDaemon(true); + } + + @Override + public void run() { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + while ((line = reader.readLine()) != null) { + output.append(line).append('\n'); + } + } catch (Exception ignored) { + } + } + + String getOutput() { + return output.toString().trim(); + } +} diff --git a/app/src/main/java/com/dhangofa/networktoggle/model/CommandResult.java b/app/src/main/java/com/dhangofa/networktoggle/model/CommandResult.java new file mode 100644 index 0000000..1997515 --- /dev/null +++ b/app/src/main/java/com/dhangofa/networktoggle/model/CommandResult.java @@ -0,0 +1,32 @@ +package com.dhangofa.networktoggle.model; + +public final class CommandResult { + private final String command; + private final int exitCode; + private final String stdout; + private final String stderr; + private final String exceptionMessage; + + private CommandResult(String command, int exitCode, String stdout, String stderr, String exceptionMessage) { + this.command = command == null ? "" : command; + this.exitCode = exitCode; + this.stdout = stdout == null ? "" : stdout; + this.stderr = stderr == null ? "" : stderr; + this.exceptionMessage = exceptionMessage == null ? "" : exceptionMessage; + } + + public static CommandResult completed(String command, int exitCode, String stdout, String stderr) { + return new CommandResult(command, exitCode, stdout, stderr, ""); + } + + public static CommandResult failed(String command, String message) { + return new CommandResult(command, -1, "", "", message); + } + + public boolean isSuccess() { return exitCode == 0; } + public String getCommand() { return command; } + public int getExitCode() { return exitCode; } + public String getStdout() { return stdout; } + public String getStderr() { return stderr; } + public String getExceptionMessage() { return exceptionMessage; } +}