Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
6a69c62
Added ShellValueParser for reusable shell-output parsing
Dhangofa Aug 11, 2026
5b4995f
Added SimResolver for subscription ID and physical-slot resolution
Dhangofa Aug 11, 2026
8341a08
Added NetworkModeReader for preferred network mode readback
Dhangofa Aug 11, 2026
1f28e25
Added NetworkModeController for applying selected network modes
Dhangofa Aug 11, 2026
2a7d448
Removed SIM resolution and telephony shell-command construction from …
Dhangofa Aug 11, 2026
0bc4193
Added ordered cycle persistence to AppPreferences
Dhangofa Aug 11, 2026
c349fd0
Added TileCycleManager for validating and navigating ordered tile cycles
Dhangofa Aug 11, 2026
841c1b4
Add Quick Tile Cycle configuration card
Dhangofa Aug 11, 2026
032f5bb
Add Quick Tile Cycle configuration card
Dhangofa Aug 11, 2026
450e9d6
Add tile cycle UI controller
Dhangofa Aug 11, 2026
0cbbcee
Integrate tile cycle controls into MainActivity
Dhangofa Aug 11, 2026
a491a5c
Use configured cycle in NetworkTileService
Dhangofa Aug 11, 2026
b5e0b36
Add Quick Tile Cycle card to main layout
Dhangofa Aug 11, 2026
072f8c8
Prevent stale readback from overriding tile state
Dhangofa Aug 11, 2026
665b767
Activated the custom NoActionBar light and dark themes from the appl…
Dhangofa Aug 11, 2026
462eaf6
Removed framework ActionBar initialization from MainActivity
Dhangofa Aug 11, 2026
525b5bf
Added shape_segmented_container for new buttons
Dhangofa Aug 11, 2026
4fe4922
Added selector_segmented_tab for new buttons
Dhangofa Aug 11, 2026
77fb2b1
Added color_segmented_text for new buttons
Dhangofa Aug 11, 2026
429638e
Update activity_main to contain new buttons, shape, developer card
Dhangofa Aug 11, 2026
58b07e3
Added new icon reference for tile cycle
Dhangofa Aug 11, 2026
dc345d4
Added refresh/cycle vector icon for the tile cycle card
Dhangofa Aug 11, 2026
ba0c471
Fix issues
Dhangofa Aug 11, 2026
7ab025b
Bind appVersionText in MainActivity
Dhangofa Aug 11, 2026
0f8616f
Added Segment control colors
Dhangofa Aug 11, 2026
652ed57
Added Segment control colors for night mode
Dhangofa Aug 11, 2026
6510f05
Remove hardcoded colors
Dhangofa Aug 11, 2026
5bd7c79
Remove hardcoded colors
Dhangofa Aug 11, 2026
57d462d
Remove hardcoded colors and change location of color_segmented_text.xml
Dhangofa Aug 11, 2026
3549a48
Update set status to change color
Dhangofa Aug 11, 2026
396018a
fix closing tags </selector>
Dhangofa Aug 11, 2026
6d9e557
Refine main screen spacing and version pill
Dhangofa Aug 11, 2026
43929f5
Fix: Show active network mode in QS tile label
Dhangofa Aug 11, 2026
c1604a7
Merge branch 'main' into dev
Dhangofa Aug 11, 2026
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
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
android:label="NetToggle"
android:icon="@drawable/ic_launcher"
android:roundIcon="@drawable/ic_launcher"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:theme="@style/Theme.NetToggle"
android:supportsRtl="true">

<receiver
Expand Down
73 changes: 30 additions & 43 deletions app/src/main/java/com/dhangofa/networktoggle/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,9 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.widget.ImageView;
import android.widget.RadioButton;
Expand All @@ -38,13 +36,18 @@
import com.dhangofa.networktoggle.config.AppPreferences;
import com.dhangofa.networktoggle.model.ExecutionMode;
import com.dhangofa.networktoggle.model.TargetSim;
import com.dhangofa.networktoggle.cycle.TileCycleManager;
import com.dhangofa.networktoggle.ui.TileCycleUiController;


import rikka.shizuku.Shizuku;

public class MainActivity extends Activity {
public class MainActivity extends Activity {

private RadioGroup radioGroup;
private RadioButton radioRoot;
private RadioButton radioShizuku;
private TextView appVersionText;
private TextView statusText;
private ImageView githubLink;
private ImageView telegramLink;
Expand All @@ -56,6 +59,7 @@ public class MainActivity extends Activity {
private TextView autoSimWarningText;

private AppPreferences appPreferences;
private TileCycleUiController tileCycleUiController;
private volatile boolean activityDestroyed;
private Thread rootCheckThread;
private Process rootCheckProcess;
Expand Down Expand Up @@ -92,55 +96,23 @@ protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activityDestroyed = false;

configureActionBar();
configureStatusBar();
setContentView(R.layout.activity_main);

appPreferences = new AppPreferences(this);
bindViews();
appVersionText.setText("v" + getAppVersionName());
bindLinks();
TileCycleManager tileCycleManager = new TileCycleManager(appPreferences);
tileCycleUiController = new TileCycleUiController(this, tileCycleManager);
tileCycleUiController.initialize();
registerShizukuListeners();
loadSavedExecutionMode();
loadSavedTargetSimMode();
updateAutoSimWarning();
bindSelectionListeners();
}

private void configureActionBar() {
if (getActionBar() == null) return;

getActionBar().setDisplayOptions(
android.app.ActionBar.DISPLAY_SHOW_TITLE
| android.app.ActionBar.DISPLAY_SHOW_CUSTOM);

TextView versionText = new TextView(this);
versionText.setText("v" + getAppVersionName());
versionText.setTextSize(12);
versionText.setTypeface(null, Typeface.BOLD);
versionText.setTextColor(getColor(R.color.brand_on_primary_container));
versionText.setBackgroundResource(R.drawable.shape_pill_badge_bg);

float density = getResources().getDisplayMetrics().density;
versionText.setPadding((int) (10 * density), (int) (4 * density),
(int) (10 * density), (int) (4 * density));

android.app.ActionBar.LayoutParams params = new android.app.ActionBar.LayoutParams(
android.app.ActionBar.LayoutParams.WRAP_CONTENT,
android.app.ActionBar.LayoutParams.WRAP_CONTENT,
Gravity.END | Gravity.CENTER_VERTICAL);
params.setMarginEnd((int) (16 * density));

getActionBar().setCustomView(versionText, params);
getActionBar().setElevation(0);

try {
int titleId = getResources().getIdentifier("action_bar_title", "id", "android");
TextView titleText = findViewById(titleId);
if (titleText != null) titleText.setTypeface(null, Typeface.BOLD);
} catch (Exception ignored) {
}
}

private void configureStatusBar() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return;

Expand All @@ -156,6 +128,7 @@ private void bindViews() {
radioRoot = findViewById(R.id.radioRoot);
radioShizuku = findViewById(R.id.radioShizuku);
statusText = findViewById(R.id.shizukuStatusText);
appVersionText = findViewById(R.id.appVersionText);
targetSimRadioGroup = findViewById(R.id.targetSimRadioGroup);
radioSimAuto = findViewById(R.id.radioSimAuto);
radioSim1 = findViewById(R.id.radioSim1);
Expand Down Expand Up @@ -282,10 +255,24 @@ private void checkShizukuPermission(boolean requestIfNeeded) {
}
}

private void setStatus(String text, int color) {
statusText.setText(text);
statusText.setTextColor(color);
}
private void setStatus(String text, int color) {
statusText.setText(text);
statusText.setTextColor(color);

if (color == 0xFF1B873F) {
statusText.setBackgroundResource(
R.drawable.shape_status_badge_success
);
} else if (color == 0xFFFF5555) {
statusText.setBackgroundResource(
R.drawable.shape_status_badge_error
);
} else {
statusText.setBackgroundResource(
R.drawable.shape_pill_badge_bg
);
}
}

private String getAppVersionName() {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.dhangofa.networktoggle.telephony.NetworkModeController;
import com.dhangofa.networktoggle.telephony.NetworkModeReader;
import com.dhangofa.networktoggle.telephony.SimResolver;
import com.dhangofa.networktoggle.cycle.TileCycleManager;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
Expand All @@ -41,12 +42,14 @@ public class NetworkTileService extends TileService {
private AppPreferences appPreferences;
private NetworkModeReader networkModeReader;
private NetworkModeController networkModeController;
private TileCycleManager tileCycleManager;

@Override
public void onCreate() {
super.onCreate();

appPreferences = new AppPreferences(this);
tileCycleManager = new TileCycleManager(appPreferences);
SimResolver simResolver = new SimResolver(appPreferences);
networkModeReader = new NetworkModeReader(appPreferences, simResolver);
networkModeController = new NetworkModeController(simResolver);
Expand All @@ -64,18 +67,25 @@ public void onStartListening() {
return;
}

EXECUTOR.execute(() -> {
NetworkMode realMode = networkModeReader.readCurrentMode();
EXECUTOR.execute(() -> {
NetworkMode realMode = networkModeReader.readCurrentMode();

mainHandler.post(() -> {
if (realMode != NetworkMode.UNKNOWN) {
appPreferences.setCachedNetworkMode(realMode);
updateTileUI(realMode);
} else {
updateTileUI(cachedMode);
}
});
mainHandler.post(() -> {
/*
* A tile click or another operation may have updated the state
* while this asynchronous readback was running.
*/
if (appPreferences.getCachedNetworkMode()
!= NetworkMode.UNKNOWN) {
return;
}

if (realMode != NetworkMode.UNKNOWN) {
appPreferences.setCachedNetworkMode(realMode);
updateTileUI(realMode);
}
});
});
}

@Override
Expand All @@ -95,7 +105,7 @@ public void onClick() {
}

NetworkMode currentMode = appPreferences.getCachedNetworkMode();
NetworkMode nextMode = NetworkMode.nextInDefaultCycle(currentMode);
NetworkMode nextMode = tileCycleManager.getNextMode(currentMode);
updateTileSwitchingUI();

EXECUTOR.execute(() -> {
Expand Down Expand Up @@ -187,16 +197,22 @@ private void updateTileSwitchingUI() {

private void updateTileUI(NetworkMode mode) {
Tile tile = getQsTile();
if (tile == null) return;

if (tile == null) {
return;
}

if (mode == NetworkMode.UNKNOWN) {
if (appPreferences.getExecutionMode() == ExecutionMode.NONE) {
tile.setState(Tile.STATE_UNAVAILABLE);
tile.setLabel("Setup Required");
} else {
NetworkMode firstMode = tileCycleManager.getFirstMode();

tile.setState(Tile.STATE_INACTIVE);
tile.setLabel(mode.getTileLabel());
tile.setLabel("Tap to Set " + firstMode.getTileLabel());
}

tile.setIcon(getCachedIcon("?"));
} else {
tile.setState(Tile.STATE_ACTIVE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@
import com.dhangofa.networktoggle.model.NetworkMode;
import com.dhangofa.networktoggle.model.TargetSim;

import java.util.ArrayList;
import java.util.List;

public final class AppPreferences {
private static final String PREFS_NAME = "NetTogglePrefs";
private static final String KEY_EXEC_MODE = "exec_mode";
private static final String KEY_TARGET_SIM = "target_sim";
private static final String KEY_NETWORK_STATE = "net_state";
private static final String KEY_AUTO_SIM_ERROR = "auto_sim_error";
private static final String KEY_TILE_CYCLE_MODES = "tile_cycle_modes";

private final SharedPreferences preferences;

Expand All @@ -26,19 +30,11 @@ public ExecutionMode getExecutionMode() {
preferences.getInt(KEY_EXEC_MODE, ExecutionMode.NONE.getValue()));
}

public void setExecutionMode(ExecutionMode mode) {
preferences.edit().putInt(KEY_EXEC_MODE, mode.getValue()).apply();
}

public TargetSim getTargetSim() {
return TargetSim.fromValue(
preferences.getInt(KEY_TARGET_SIM, TargetSim.AUTO.getValue()));
}

public void setTargetSim(TargetSim targetSim) {
preferences.edit().putInt(KEY_TARGET_SIM, targetSim.getValue()).apply();
}

public NetworkMode getCachedNetworkMode() {
return NetworkMode.fromStateValue(
preferences.getInt(KEY_NETWORK_STATE, NetworkMode.UNKNOWN.getStateValue()));
Expand All @@ -48,6 +44,10 @@ public void setCachedNetworkMode(NetworkMode mode) {
preferences.edit().putInt(KEY_NETWORK_STATE, mode.getStateValue()).apply();
}

public void clearCachedNetworkMode() {
setCachedNetworkMode(NetworkMode.UNKNOWN);
}

public boolean hasAutoSimError() {
return preferences.getBoolean(KEY_AUTO_SIM_ERROR, false);
}
Expand All @@ -72,6 +72,31 @@ public void onTargetSimChanged(TargetSim targetSim) {
.apply();
}

public List<NetworkMode> getTileCycleModes() {
String saved = preferences.getString(KEY_TILE_CYCLE_MODES, "");
List<NetworkMode> modes = new ArrayList<>();
if (saved == null || saved.trim().isEmpty()) return modes;

String[] ids = saved.split(",");
for (String id : ids) {
try {
NetworkMode mode = NetworkMode.valueOf(id.trim());
if (mode != NetworkMode.UNKNOWN && !modes.contains(mode)) modes.add(mode);
} catch (IllegalArgumentException ignored) {
}
}
return modes;
}

public void setTileCycleModes(List<NetworkMode> modes) {
StringBuilder value = new StringBuilder();
for (NetworkMode mode : modes) {
if (value.length() > 0) value.append(',');
value.append(mode.name());
}
preferences.edit().putString(KEY_TILE_CYCLE_MODES, value.toString()).apply();
}

public void clearTransientState() {
preferences.edit()
.putInt(KEY_NETWORK_STATE, NetworkMode.UNKNOWN.getStateValue())
Expand Down
Loading