Skip to content
Merged
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -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.**
209 changes: 165 additions & 44 deletions app/src/main/java/com/dhangofa/networktoggle/MainActivity.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);

Expand All @@ -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);
}
}
}
Loading
Loading