Skip to content
135 changes: 23 additions & 112 deletions app/src/main/java/com/dhangofa/networktoggle/NetworkTileService.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}

Expand All @@ -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();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.dhangofa.networktoggle.command;

import com.dhangofa.networktoggle.model.CommandResult;

public interface CommandExecutor {
CommandResult execute(String command);
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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; }
}
Loading