Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
789292d
Update color palette for module cards and dynamic themes
Dhangofa Aug 15, 2026
8ba2e1a
Update night color palette for module cards and pure black themes
Dhangofa Aug 15, 2026
2856ae1
Update night theme status bar color for improved layout blending
Dhangofa Aug 15, 2026
b409de4
Update light theme status bar color for improved layout blending
Dhangofa Aug 15, 2026
00aa064
Add state list text colors for custom themed segmented text exec
Dhangofa Aug 15, 2026
ea9fa5a
Add state list text colors for custom themed segmented text cycle
Dhangofa Aug 15, 2026
9c0cfdd
Add general app icon background shape
Dhangofa Aug 15, 2026
4071b31
Add adaptive icon foreground layer
Dhangofa Aug 15, 2026
4b3f696
Add Execution Mode card background
Dhangofa Aug 15, 2026
971e5fb
Add general app icon background shape
Dhangofa Aug 15, 2026
ef42907
Add Quick Tile Cycle card background
Dhangofa Aug 15, 2026
bdfa024
Add Developer Profile floating footer background
Dhangofa Aug 15, 2026
49818a7
Add Execution Mode segmented tab selector
Dhangofa Aug 15, 2026
1c9650e
Add Quick Tile Cycle segmented tab selector
Dhangofa Aug 15, 2026
1ecfe2e
Add rectangular segmented tab selector
Dhangofa Aug 15, 2026
f565c08
Add rectangular segmented container shape
Dhangofa Aug 15, 2026
6930b6e
Add small horizontal divider shape
Dhangofa Aug 15, 2026
fd182c5
Add cycle order text badge background
Dhangofa Aug 15, 2026
3f16524
Delete app/src/main/res/drawable/shape_radio_item_bg.xml
Dhangofa Aug 15, 2026
cde589a
Modernize section outline styling with softer curves
Dhangofa Aug 15, 2026
2493bb6
Update segmented tab selector to fully rounded pills
Dhangofa Aug 15, 2026
d6c138a
Soften Quick Settings hint banner styling
Dhangofa Aug 15, 2026
59f4c9b
Update segmented container to fully rounded pill shape
Dhangofa Aug 15, 2026
405268e
Update social icon background from oval to rounded squircle
Dhangofa Aug 15, 2026
882b457
Refactor portrait layout and upgrade header styling
Dhangofa Aug 15, 2026
7aae958
Redesign Quick Tile Cycle card into 2x3 segmented grids
Dhangofa Aug 15, 2026
7b32ccc
Create modular Execution Mode and Target SIM card layout
Dhangofa Aug 15, 2026
55d3c0b
Add & Refactor landscape layout for modular side-by-side cards
Dhangofa Aug 15, 2026
7885a3e
Upgrade TileCycleUiController for 2x3 grid and dynamic separators
Dhangofa Aug 15, 2026
2c65e44
Upgrade MainActivity for dynamic segmented separators and UI tweaks
Dhangofa Aug 15, 2026
392533a
Add support for 3G Preferred and 2G Only modes
Dhangofa Aug 15, 2026
4b4ebd2
Add 3G Preferred and 2G Only icon caching
Dhangofa Aug 15, 2026
4644d36
Correct Pref 3G/4G binary masks to Mode 21 and 22
Dhangofa Aug 15, 2026
2c182b4
Implement dynamic network capability caching
Dhangofa Aug 15, 2026
222584a
Created a brand-new registry class to manage Stage 4 of our resolutio…
Dhangofa Aug 16, 2026
e8bd84e
Upgrade shell parser to support key-based extraction
Dhangofa Aug 16, 2026
d16621c
Implement 4-stage robust network capability resolution
Dhangofa Aug 16, 2026
3966069
Add READ_PHONE_STATE permission
Dhangofa Aug 16, 2026
c256dce
Add diagnostic tracking, capability cache and explicit tile error states
Dhangofa Aug 16, 2026
9102fbb
Implement dynamic capability syncing and diagnostic UI
Dhangofa Aug 16, 2026
409991f
Implement passive Shizuku checks and error state UI
Dhangofa Aug 16, 2026
ab02a8d
Implement native Settings API bypass for network reads
Dhangofa Aug 16, 2026
274df9b
Leverage native SubscriptionManager for SIM resolution
Dhangofa Aug 16, 2026
8f96520
Enhance slot capability caching behavior
Dhangofa Aug 16, 2026
95e7230
Add dynamic UI capability filtering to cycle checkboxes
Dhangofa Aug 16, 2026
3fb6ce3
Create DiagnosticError data model
Dhangofa Aug 16, 2026
b1f0296
Implement shell error formatting utility
Dhangofa Aug 16, 2026
8dcd096
Add warning icon drawable
Dhangofa Aug 16, 2026
d890b04
Add speed/performance icon drawable
Dhangofa Aug 16, 2026
d362a7d
Add directional arrow drawable
Dhangofa Aug 16, 2026
6b689e8
Add rounded dialog background shape
Dhangofa Aug 16, 2026
ad8a09c
Add layout for the main screen error alert banner
Dhangofa Aug 16, 2026
13fed81
Add layout for READ_PHONE_STATE permission prompt
Dhangofa Aug 16, 2026
664c6ff
Add layout for shell error reporting dialog
Dhangofa Aug 16, 2026
4a69928
Inject diagnostic error banner layout
Dhangofa Aug 16, 2026
644e825
Integrate network capability filtering into tile cycle UI
Dhangofa Aug 16, 2026
ecaa1dc
Remove action bar styling constraint
Dhangofa Aug 16, 2026
dbd61c3
Inject diagnostic error banner layout in landscape mode
Dhangofa Aug 16, 2026
1b8adb0
Add android:id="@+id/targetSimHeaderContainer" attribute
Dhangofa Aug 16, 2026
2866cfd
Added button shape
Dhangofa Aug 16, 2026
29b8fdb
Expand LTEandAbove carrier registry with global 2G/3G sunset data
Dhangofa Aug 16, 2026
29187a0
Updated version code and name
Dhangofa Aug 16, 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
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ android {
applicationId = "com.dhangofa.networktoggle"
minSdk = 24
targetSdk = 36
versionCode = 32
versionName = "1.0.32"
versionCode = 65
versionName = "1.0.65"
}
// Suggested by IzzyOnDroid
dependenciesInfo {
Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<!-- Used only to clear stale cached QS tile state after reboot -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />

<application
android:allowBackup="false"
Expand Down
310 changes: 305 additions & 5 deletions app/src/main/java/com/dhangofa/networktoggle/MainActivity.java

Large diffs are not rendered by default.

164 changes: 131 additions & 33 deletions app/src/main/java/com/dhangofa/networktoggle/NetworkTileService.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package com.dhangofa.networktoggle;
import android.os.Build;

import android.graphics.Bitmap;
import android.graphics.Canvas;
Expand All @@ -10,6 +11,10 @@
import android.os.Looper;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import android.content.Intent;
import android.app.PendingIntent;
import rikka.shizuku.Shizuku;
import android.content.pm.PackageManager;
import android.widget.Toast;

import com.dhangofa.networktoggle.config.AppPreferences;
Expand All @@ -35,29 +40,50 @@ public class NetworkTileService extends TileService {
private static Icon icon5g;
private static Icon iconP5g;
private static Icon iconP4g;
private static Icon iconP3g;
private static Icon icon2g;
private static Icon iconUnknown;

private final Handler mainHandler = new Handler(Looper.getMainLooper());

private AppPreferences appPreferences;
private NetworkModeReader networkModeReader;
private NetworkModeController networkModeController;
private TileCycleManager tileCycleManager;
private TileCycleManager tileCycleManager;
private com.dhangofa.networktoggle.telephony.SimResolver simResolver;

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

// Init dependencies
appPreferences = new AppPreferences(this);
tileCycleManager = new TileCycleManager(appPreferences);
SimResolver simResolver = new SimResolver(appPreferences);
networkModeReader = new NetworkModeReader(appPreferences, simResolver);
tileCycleManager = new TileCycleManager(appPreferences);
simResolver = new com.dhangofa.networktoggle.telephony.SimResolver(this, appPreferences);
networkModeReader = new com.dhangofa.networktoggle.telephony.NetworkModeReader(this, appPreferences, simResolver);
networkModeController = new NetworkModeController(simResolver);
}

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

// Passive Shizuku Check
if (appPreferences.getExecutionMode() == ExecutionMode.SHIZUKU) {
boolean isShizukuOk = false;
try {
isShizukuOk = rikka.shizuku.Shizuku.pingBinder() && rikka.shizuku.Shizuku.checkSelfPermission() == android.content.pm.PackageManager.PERMISSION_GRANTED;
} catch (Throwable t) {
isShizukuOk = false;
}

int currentError = appPreferences.getTileErrorState();
if (!isShizukuOk && currentError != AppPreferences.TILE_ERROR_SHIZUKU) {
appPreferences.setTileErrorState(AppPreferences.TILE_ERROR_SHIZUKU);
} else if (isShizukuOk && currentError == AppPreferences.TILE_ERROR_SHIZUKU) {
appPreferences.setTileErrorState(AppPreferences.TILE_ERROR_NONE);
}
}

NetworkMode cachedMode = appPreferences.getCachedNetworkMode();
updateTileUI(cachedMode);
Expand All @@ -67,25 +93,25 @@ public void onStartListening() {
return;
}

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

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;
}
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);
}
if (realMode != NetworkMode.UNKNOWN) {
appPreferences.setCachedNetworkMode(realMode);
updateTileUI(realMode);
}
});
});
});
}

@Override
Expand All @@ -109,27 +135,70 @@ public void onClick() {
updateTileSwitchingUI();

EXECUTOR.execute(() -> {
int slotIndex = simResolver.resolveTargetSlotIndex(executionMode);
if (!simResolver.isValidSlotIndex(slotIndex)) {
mainHandler.post(() -> {
Toast.makeText(getApplicationContext(), "No SIM card found in target slot.", Toast.LENGTH_SHORT).show();
appPreferences.onTargetSimChanged(com.dhangofa.networktoggle.model.TargetSim.AUTO);
updateTileUI(appPreferences.getCachedNetworkMode());
IS_SWITCHING.set(false);
});
return;
}

CommandResult result = networkModeController.apply(
nextMode,
executionMode
);

mainHandler.post(() -> {
try {
if (result.isSuccess()) {
appPreferences.setCachedNetworkMode(nextMode);
appPreferences.setAutoSimError(false);
updateTileUI(nextMode);
} else {
updateTileUI(currentMode);
if (appPreferences.hasAutoSimError()) {
showAutoSimErrorToast();
if (result.isSuccess()) {
appPreferences.setCachedNetworkMode(nextMode);
appPreferences.setAutoSimError(false);
appPreferences.setTileErrorState(AppPreferences.TILE_ERROR_NONE);
mainHandler.post(() -> {
updateTileUI(nextMode);
IS_SWITCHING.set(false);
});
} else {
// Command failed! Check for permission failures first.
boolean isAuthError = false;
if (executionMode == ExecutionMode.SHIZUKU) {
try {
if (!Shizuku.pingBinder() || Shizuku.checkSelfPermission() != PackageManager.PERMISSION_GRANTED) {
isAuthError = true;
appPreferences.setTileErrorState(AppPreferences.TILE_ERROR_SHIZUKU);
}
} catch (Throwable t) {
isAuthError = true;
appPreferences.setTileErrorState(AppPreferences.TILE_ERROR_SHIZUKU);
}
} else if (executionMode == ExecutionMode.ROOT) {
try {
Process p = Runtime.getRuntime().exec(new String[]{"su", "-c", "true"});
int exitCode = p.waitFor();
if (exitCode != 0) {
isAuthError = true;
appPreferences.setTileErrorState(AppPreferences.TILE_ERROR_ROOT);
}
} catch (Exception e) {
isAuthError = true;
appPreferences.setTileErrorState(AppPreferences.TILE_ERROR_ROOT);
}
} finally {
IS_SWITCHING.set(false);
}
});

if (!isAuthError) {
appPreferences.setTileErrorState(AppPreferences.TILE_ERROR_CMD);
appPreferences.setLastError(result.getCommand(), result.getStderr());
}

mainHandler.post(() -> {
updateTileUI(currentMode);
if (appPreferences.hasAutoSimError()) {
showAutoSimErrorToast();
}
IS_SWITCHING.set(false);
});
}
});
}

Expand Down Expand Up @@ -177,6 +246,12 @@ private Icon getCachedIcon(String text) {
case "P4G":
if (iconP4g == null) iconP4g = createTextOnlyIcon("P4G");
return iconP4g;
case "P3G":
if (iconP3g == null) iconP3g = createTextOnlyIcon("P3G");
return iconP3g;
case "2G":
if (icon2g == null) icon2g = createTextOnlyIcon("2G");
return icon2g;
default:
if (iconUnknown == null) {
iconUnknown = createTextOnlyIcon("?");
Expand All @@ -202,6 +277,27 @@ private void updateTileUI(NetworkMode mode) {
return;
}

int errorState = appPreferences.getTileErrorState();
if (errorState == AppPreferences.TILE_ERROR_SHIZUKU) {
tile.setState(Tile.STATE_UNAVAILABLE);
tile.setLabel("Shizuku Unavailable");
tile.setIcon(getCachedIcon("?"));
tile.updateTile();
return;
} else if (errorState == AppPreferences.TILE_ERROR_ROOT) {
tile.setState(Tile.STATE_UNAVAILABLE);
tile.setLabel("Root Unavailable");
tile.setIcon(getCachedIcon("?"));
tile.updateTile();
return;
} else if (errorState == AppPreferences.TILE_ERROR_CMD) {
tile.setState(Tile.STATE_INACTIVE);
tile.setLabel("Error Check App");
tile.setIcon(getCachedIcon("?"));
tile.updateTile();
return;
}

if (mode == NetworkMode.UNKNOWN) {
if (appPreferences.getExecutionMode() == ExecutionMode.NONE) {
tile.setState(Tile.STATE_UNAVAILABLE);
Expand All @@ -223,6 +319,8 @@ private void updateTileUI(NetworkMode mode) {
tile.updateTile();
}



private void showAutoSimErrorToast() {
Toast.makeText(
this,
Expand Down
Loading