Skip to content
Open
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
38 changes: 21 additions & 17 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ android {
versionName = "1.0.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

vectorDrawables {
useSupportLibrary = true
}

resourceConfigurations += listOf("en", "xxhdpi")
}

Expand All @@ -39,20 +39,20 @@ android {
isJniDebuggable = false
isPseudoLocalesEnabled = false
}

debug {
isMinifyEnabled = false
isDebuggable = true
applicationIdSuffix = ".debug"
versionNameSuffix = "-debug"
}
}

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

kotlinOptions {
jvmTarget = "11"
freeCompilerArgs += listOf(
Expand All @@ -63,38 +63,42 @@ android {
"-Xno-receiver-assertions"
)
}

buildFeatures {
compose = true
aidl = true
buildConfig = true
}

composeOptions {
kotlinCompilerExtensionVersion = libs.versions.compose.get()
}

testOptions {
unitTests {
isReturnDefaultValues = true
isIncludeAndroidResources = true
}
animationsDisabled = true
}

packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
excludes += "/META-INF/*.kotlin_module"
excludes += "/META-INF/maven/**"
excludes += "/androidsupportmultidexversion.txt"
}

jniLibs {
useLegacyPackaging = false
}
}


lint {
disable += "NullSafeMutableLiveData"
}

// Bundle configuration for APK size optimization
bundle {
language {
Expand All @@ -113,34 +117,34 @@ dependencies {

implementation(libs.androidx.foundation)
compileOnly(project(":hiddenapi"))

// Root & Shizuku
implementation(libs.libsu.core)
implementation(libs.libsu.service)
implementation(libs.shizuku.api)
implementation(libs.shizuku.provider)

// Core Android
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)

// Compose
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.material3)
implementation(libs.androidx.material.icons.extended)
implementation(libs.androidx.lifecycle.viewmodel.compose)

// Dependency Injection
implementation(libs.hilt.android)
implementation(libs.hilt.navigation.compose)
kapt(libs.hilt.compiler)

// Data Storage
implementation(libs.androidx.datastore.preferences)

// Testing
testImplementation(libs.junit)
testImplementation(libs.mockk)
Expand Down
36 changes: 15 additions & 21 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,27 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.NetworkSwitch"
tools:targetApi="31">

android:enableOnBackInvokedCallback="true"
tools:targetApi="33">

<activity
android:name=".presentation.ui.activity.MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.NetworkSwitch">
</activity>

<activity-alias
android:name="com.supernova.networkswitch.presentation.ui.activity.Launcher"
android:exported="true"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:targetActivity=".presentation.ui.activity.MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity
android:name=".presentation.ui.activity.SettingsActivity"
android:exported="false"
android:label="Settings"
android:parentActivityName=".presentation.ui.activity.MainActivity"
android:theme="@style/Theme.NetworkSwitch" />

<activity
android:name=".presentation.ui.activity.NetworkModeConfigActivity"
android:exported="false"
android:label="@string/network_mode_config"
android:parentActivityName=".presentation.ui.activity.MainActivity"
android:theme="@style/Theme.NetworkSwitch" />

</activity-alias>
<service
android:name=".service.NetworkTileService"
android:exported="true"
Expand All @@ -49,7 +43,7 @@
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>

<!-- Shizuku Provider -->
<provider
android:name="rikka.shizuku.ShizukuProvider"
Expand All @@ -58,7 +52,7 @@
android:enabled="true"
android:exported="true"
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />

</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -1,7 +1,34 @@
package com.supernova.networkswitch

import android.app.Application
import com.supernova.networkswitch.data.source.PreferencesDataSource
import com.supernova.networkswitch.presentation.LauncherIcon
import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import javax.inject.Inject

@HiltAndroidApp
class NetworkSwitchApplication : Application()
class NetworkSwitchApplication : Application() {

@Inject
lateinit var preferencesDataSource: PreferencesDataSource

private val applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

override fun onCreate() {
super.onCreate()
observeLauncherIconState()
}

private fun observeLauncherIconState() {
preferencesDataSource.observeHideLauncherIcon()
.onEach { hide ->
LauncherIcon.setEnabled(this, !hide)
}
.launchIn(applicationScope)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ class NetworkControlRepositoryImpl @Inject constructor(
shizukuDataSource.resetConnection()
}

override suspend fun requestPermission(method: ControlMethod): Boolean {
val dataSource = getDataSource(method)
return dataSource.requestPermission()
}

private fun getDataSource(method: ControlMethod): NetworkControlDataSource {
return when (method) {
ControlMethod.ROOT -> rootDataSource
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,36 @@ import javax.inject.Singleton
class PreferencesRepositoryImpl @Inject constructor(
private val preferencesDataSource: PreferencesDataSource
) : PreferencesRepository {

override suspend fun getControlMethod(): ControlMethod {
return preferencesDataSource.getControlMethod()
}

override suspend fun setControlMethod(method: ControlMethod) {
preferencesDataSource.setControlMethod(method)
}

override fun observeControlMethod(): Flow<ControlMethod> {
return preferencesDataSource.observeControlMethod()
}

override suspend fun getToggleModeConfig(): ToggleModeConfig {
return preferencesDataSource.getToggleModeConfig()
}

override suspend fun setToggleModeConfig(config: ToggleModeConfig) {
preferencesDataSource.setToggleModeConfig(config)
}

override fun observeToggleModeConfig(): Flow<ToggleModeConfig> {
return preferencesDataSource.observeToggleModeConfig()
}

override fun observeHideLauncherIcon(): Flow<Boolean> {
return preferencesDataSource.observeHideLauncherIcon()
}

override suspend fun setHideLauncherIcon(hide: Boolean) {
preferencesDataSource.setHideLauncherIcon(hide)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ interface NetworkControlDataSource {
suspend fun setNetworkMode(subId: Int, mode: NetworkMode)
fun isConnected(): Boolean
fun resetConnection()
suspend fun requestPermission(): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,76 +19,90 @@ import javax.inject.Singleton
class PreferencesDataSource @Inject constructor(
private val dataStore: DataStore<Preferences>
) {

companion object {
private val HIDE_LAUNCHER_ICON_KEY = booleanPreferencesKey("hide_launcher_icon")
private val CONTROL_METHOD_KEY = stringPreferencesKey("control_method")
private val TOGGLE_MODE_A_KEY = intPreferencesKey("toggle_mode_a")
private val TOGGLE_MODE_B_KEY = intPreferencesKey("toggle_mode_b")
private val TOGGLE_NEXT_IS_B_KEY = booleanPreferencesKey("toggle_next_is_b")


private const val DEFAULT_HIDE_LAUNCHER_ICON = false
private const val DEFAULT_CONTROL_METHOD = "SHIZUKU"

private val DEFAULT_MODE_A = NetworkMode.LTE_ONLY
private val DEFAULT_MODE_B = NetworkMode.NR_ONLY
private const val DEFAULT_NEXT_IS_B = true
}


suspend fun setHideLauncherIcon(hide: Boolean) {
dataStore.edit { preferences ->
preferences[HIDE_LAUNCHER_ICON_KEY] = hide
}
}

fun observeHideLauncherIcon(): Flow<Boolean> {
return dataStore.data.map { preferences ->
preferences[HIDE_LAUNCHER_ICON_KEY] ?: DEFAULT_HIDE_LAUNCHER_ICON
}
}

private fun parseControlMethod(methodString: String?): ControlMethod {
return try {
ControlMethod.valueOf(methodString ?: DEFAULT_CONTROL_METHOD)
} catch (e: IllegalArgumentException) {
ControlMethod.SHIZUKU
}
}

suspend fun getControlMethod(): ControlMethod {
return dataStore.data.map { preferences ->
parseControlMethod(preferences[CONTROL_METHOD_KEY])
}.first()
}

suspend fun setControlMethod(method: ControlMethod) {
dataStore.edit { preferences ->
preferences[CONTROL_METHOD_KEY] = method.name
}
}

fun observeControlMethod(): Flow<ControlMethod> {
return dataStore.data.map { preferences ->
parseControlMethod(preferences[CONTROL_METHOD_KEY])
}
}

suspend fun getToggleModeConfig(): ToggleModeConfig {
return dataStore.data.map { preferences ->
val modeAValue = preferences[TOGGLE_MODE_A_KEY] ?: DEFAULT_MODE_A.value
val modeBValue = preferences[TOGGLE_MODE_B_KEY] ?: DEFAULT_MODE_B.value
val nextIsB = preferences[TOGGLE_NEXT_IS_B_KEY] ?: DEFAULT_NEXT_IS_B

val modeA = NetworkMode.fromValue(modeAValue) ?: DEFAULT_MODE_A
val modeB = NetworkMode.fromValue(modeBValue) ?: DEFAULT_MODE_B

ToggleModeConfig(modeA, modeB, nextIsB)
}.first()
}

suspend fun setToggleModeConfig(config: ToggleModeConfig) {
dataStore.edit { preferences ->
preferences[TOGGLE_MODE_A_KEY] = config.modeA.value
preferences[TOGGLE_MODE_B_KEY] = config.modeB.value
preferences[TOGGLE_NEXT_IS_B_KEY] = config.nextModeIsB
}
}

fun observeToggleModeConfig(): Flow<ToggleModeConfig> {
return dataStore.data.map { preferences ->
val modeAValue = preferences[TOGGLE_MODE_A_KEY] ?: DEFAULT_MODE_A.value
val modeBValue = preferences[TOGGLE_MODE_B_KEY] ?: DEFAULT_MODE_B.value
val nextIsB = preferences[TOGGLE_NEXT_IS_B_KEY] ?: DEFAULT_NEXT_IS_B

val modeA = NetworkMode.fromValue(modeAValue) ?: DEFAULT_MODE_A
val modeB = NetworkMode.fromValue(modeBValue) ?: DEFAULT_MODE_B

ToggleModeConfig(modeA, modeB, nextIsB)
}
}
Expand Down
Loading