Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 14 additions & 22 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.hilt)
alias(libs.plugins.kotlin.kapt)
alias(libs.plugins.legacy.kapt)
}

android {
namespace = "com.supernova.networkswitch"
compileSdk = 36
compileSdk = 35

defaultConfig {
applicationId = "com.supernova.networkswitch"
minSdk = 29
targetSdk = 36
versionCode = 1
versionName = "1.0.0"
targetSdk = 35
versionCode = 2
versionName = "1.0.1"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

vectorDrawables {
useSupportLibrary = true
}

resourceConfigurations += listOf("en", "xxhdpi")
androidResources {
localeFilters += "en"
}
}

buildTypes {
Expand All @@ -49,19 +50,8 @@ android {
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}

kotlinOptions {
jvmTarget = "11"
freeCompilerArgs += listOf(
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
"-Xjvm-default=all",
"-Xno-param-assertions",
"-Xno-call-assertions",
"-Xno-receiver-assertions"
)
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

buildFeatures {
Expand Down Expand Up @@ -123,7 +113,9 @@ dependencies {
// Core Android
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.process)
implementation(libs.androidx.activity.compose)
implementation(libs.hiddenapibypass)

// Compose
implementation(platform(libs.androidx.compose.bom))
Expand All @@ -136,7 +128,7 @@ dependencies {
// Dependency Injection
implementation(libs.hilt.android)
implementation(libs.hilt.navigation.compose)
kapt(libs.hilt.compiler)
"kapt"(libs.hilt.compiler)

// Data Storage
implementation(libs.androidx.datastore.preferences)
Expand All @@ -154,5 +146,5 @@ dependencies {

// Hilt Testing
testImplementation(libs.hilt.android.testing)
kaptTest(libs.hilt.compiler)
"kaptTest"(libs.hilt.compiler)
}
14 changes: 14 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_BASIC_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" />

<uses-feature android:name="android.hardware.telephony" android:required="false" />

<application
android:name=".NetworkSwitchApplication"
android:allowBackup="true"
Expand Down Expand Up @@ -48,6 +56,12 @@
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
<meta-data
android:name="android.service.quicksettings.ACTIVE_TILE"
android:value="true" />
<meta-data
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
android:value="true" />
</service>

<!-- Shizuku Provider -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ package com.supernova.networkswitch

import android.app.Application
import dagger.hilt.android.HiltAndroidApp
import org.lsposed.hiddenapibypass.HiddenApiBypass
import android.os.Build

@HiltAndroidApp
class NetworkSwitchApplication : Application()
class NetworkSwitchApplication : Application() {
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
HiddenApiBypass.setHiddenApiExemptions("Landroid/telephony/", "Lcom/android/internal/telephony/")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,13 @@ class ShizukuNetworkControlDataSource @Inject constructor(
}

// Check if Shizuku permission is granted
if (Shizuku.checkSelfPermission() != PackageManager.PERMISSION_GRANTED) {
val permission = try {
Shizuku.checkSelfPermission()
} catch (e: Exception) {
PackageManager.PERMISSION_DENIED
}

if (permission != PackageManager.PERMISSION_GRANTED) {
return CompatibilityState.PermissionDenied(com.supernova.networkswitch.domain.model.ControlMethod.SHIZUKU)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import com.supernova.networkswitch.domain.usecase.GetToggleModeConfigUseCase
import com.supernova.networkswitch.domain.repository.PreferencesRepository
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.*
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import javax.inject.Inject

@AndroidEntryPoint
Expand Down Expand Up @@ -56,22 +58,47 @@ class NetworkTileService : TileService() {

override fun onClick() {
super.onClick()
android.util.Log.d("NetworkSwitch", "Tile: onClick triggered")
android.widget.Toast.makeText(this, "Switching network...", android.widget.Toast.LENGTH_SHORT).show()

// Provide immediate visual feedback
qsTile?.let {
it.state = Tile.STATE_UNAVAILABLE
it.subtitle = "Processing..."
it.updateTile()
}

if (isLocked) {
android.util.Log.d("NetworkSwitch", "Tile: Device is locked, unlocking and running...")
unlockAndRun {
performToggle()
}
} else {
performToggle()
}
}

private fun performToggle() {
val subId = SubscriptionManager.getDefaultDataSubscriptionId()
android.util.Log.d("NetworkSwitch", "Tile: performToggle for subId: $subId")

serviceScope.launch {
// Use a more persistent scope to ensure work completes even if service is destroyed
CoroutineScope(ProcessLifecycleOwner.get().lifecycleScope.coroutineContext + Dispatchers.IO).launch {
try {
toggleNetworkModeUseCase(subId)
.onSuccess { newMode ->
android.util.Log.d("NetworkSwitch", "Tile: Toggle success, new mode: ${newMode.displayName}")
currentNetworkMode = newMode
withContext(Dispatchers.Main) {
updateTile()
updateTileState()
}
}
.onFailure {
.onFailure { e ->
android.util.Log.e("NetworkSwitch", "Tile: Toggle failed", e)
refreshNetworkState()
}
} catch (_: Exception) {
} catch (e: Exception) {
android.util.Log.e("NetworkSwitch", "Tile: Error in performToggle", e)
refreshNetworkState()
}
}
Expand All @@ -85,36 +112,38 @@ class NetworkTileService : TileService() {
.onSuccess { networkMode ->
currentNetworkMode = networkMode
withContext(Dispatchers.Main) {
updateTile()
updateTileState()
}
}
} catch (_: Exception) {
// Handle errors silently
}
}

private fun updateTile() {
private fun updateTileState() {
try {
qsTile?.apply {
val config = toggleConfig
val tile = qsTile ?: return
val config = toggleConfig

if (config != null) {
tile.state = Tile.STATE_ACTIVE

if (config != null) {
state = Tile.STATE_ACTIVE

// Show current and next modes
val currentMode = config.getCurrentMode()
val nextMode = config.getNextMode()
label = currentMode.displayName
subtitle = "${nextMode.displayName}"
} else {
state = Tile.STATE_INACTIVE
label = "Network Mode"
subtitle = "Config not loaded"
}
updateTile()
// Show current mode as label and next mode as subtitle
val currentMode = currentNetworkMode ?: config.getCurrentMode()
tile.label = currentMode.displayName
tile.subtitle = "Next: ${config.getNextMode().displayName}"

// Update icon based on mode if needed
// tile.icon = Icon.createWithResource(this, R.drawable.ic_...)
} else {
tile.state = Tile.STATE_INACTIVE
tile.label = "Network Switch"
tile.subtitle = "Tap to load"
}
} catch (_: Exception) {
// Handle tile update errors silently
tile.updateTile()
android.util.Log.d("NetworkSwitch", "Tile: updateTileState finished")
} catch (e: Exception) {
android.util.Log.e("NetworkSwitch", "Tile: Failed to update tile state", e)
}
}

Expand Down
Loading
Loading