diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1b3132a..68137ea 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,21 +1,20 @@ 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" @@ -23,7 +22,9 @@ android { useSupportLibrary = true } - resourceConfigurations += listOf("en", "xxhdpi") + androidResources { + localeFilters += "en" + } } buildTypes { @@ -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 { @@ -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)) @@ -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) @@ -154,5 +146,5 @@ dependencies { // Hilt Testing testImplementation(libs.hilt.android.testing) - kaptTest(libs.hilt.compiler) + "kaptTest"(libs.hilt.compiler) } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3552c09..ff6dcb9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,14 @@ + + + + + + + + + + diff --git a/app/src/main/java/com/supernova/networkswitch/NetworkSwitchApplication.kt b/app/src/main/java/com/supernova/networkswitch/NetworkSwitchApplication.kt index 31f5406..f724e4c 100644 --- a/app/src/main/java/com/supernova/networkswitch/NetworkSwitchApplication.kt +++ b/app/src/main/java/com/supernova/networkswitch/NetworkSwitchApplication.kt @@ -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/") + } + } +} diff --git a/app/src/main/java/com/supernova/networkswitch/data/source/ShizukuNetworkControlDataSource.kt b/app/src/main/java/com/supernova/networkswitch/data/source/ShizukuNetworkControlDataSource.kt index 90e7e8e..3cf8e39 100644 --- a/app/src/main/java/com/supernova/networkswitch/data/source/ShizukuNetworkControlDataSource.kt +++ b/app/src/main/java/com/supernova/networkswitch/data/source/ShizukuNetworkControlDataSource.kt @@ -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) } diff --git a/app/src/main/java/com/supernova/networkswitch/service/NetworkTileService.kt b/app/src/main/java/com/supernova/networkswitch/service/NetworkTileService.kt index 0fa943d..aae502a 100644 --- a/app/src/main/java/com/supernova/networkswitch/service/NetworkTileService.kt +++ b/app/src/main/java/com/supernova/networkswitch/service/NetworkTileService.kt @@ -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 @@ -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() } } @@ -85,7 +112,7 @@ class NetworkTileService : TileService() { .onSuccess { networkMode -> currentNetworkMode = networkMode withContext(Dispatchers.Main) { - updateTile() + updateTileState() } } } catch (_: Exception) { @@ -93,28 +120,30 @@ class NetworkTileService : TileService() { } } - 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) } } diff --git a/app/src/main/java/com/supernova/networkswitch/service/RootNetworkControllerService.kt b/app/src/main/java/com/supernova/networkswitch/service/RootNetworkControllerService.kt index a582be3..ceb040a 100644 --- a/app/src/main/java/com/supernova/networkswitch/service/RootNetworkControllerService.kt +++ b/app/src/main/java/com/supernova/networkswitch/service/RootNetworkControllerService.kt @@ -3,185 +3,281 @@ package com.supernova.networkswitch.service import android.content.Context import android.content.Intent import android.os.Build +import android.os.IBinder import android.os.ServiceManager -import com.android.internal.telephony.ITelephony +import android.util.Log import com.supernova.networkswitch.IRootController import com.topjohnwu.superuser.ipc.RootService +import org.lsposed.hiddenapibypass.HiddenApiBypass class RootNetworkControllerService : RootService() { - + + init { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + try { + HiddenApiBypass.setHiddenApiExemptions("Landroid/telephony/", "Lcom/android/internal/telephony/") + } catch (e: Throwable) { + Log.e("NetworkSwitch", "Root: HiddenApiBypass failed", e) + } + } + } + companion object { - private val iTelephony: ITelephony by lazy { - ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE)) + private const val TAG = "NetworkSwitch" + + private fun getITelephony(): Any? { + return try { + val binder = ServiceManager.getService(Context.TELEPHONY_SERVICE) + val stubClass = Class.forName("com.android.internal.telephony.ITelephony\$Stub") + val asInterfaceMethod = stubClass.getMethod("asInterface", IBinder::class.java) + asInterfaceMethod.invoke(null, binder) + } catch (e: Exception) { + Log.e(TAG, "Root: Failed to get ITelephony", e) + null + } } - - private val reasonUser: Int by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("ALLOWED_NETWORK_TYPES_REASON_USER") - .getInt(null) + + private fun getStaticLongField(className: String, fieldName: String, fallback: Long): Long { + return try { + val clazz = Class.forName(className) + val field = clazz.getDeclaredField(fieldName) + field.isAccessible = true + field.getLong(null) + } catch (_: Exception) { + fallback + } } - - private fun getBitmask(fieldName: String): Long { + + private fun getStaticIntField(className: String, fieldName: String, fallback: Int): Int { return try { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField(fieldName) - .getLong(null) - } catch (e: Exception) { - 0L + val clazz = Class.forName(className) + val field = clazz.getDeclaredField(fieldName) + field.isAccessible = true + field.getInt(null) + } catch (_: Exception) { + fallback } } - - private val bitmasks = mapOf( - "GSM" to lazy { getBitmask("NETWORK_TYPE_BITMASK_GSM") }, - "GPRS" to lazy { getBitmask("NETWORK_TYPE_BITMASK_GPRS") }, - "EDGE" to lazy { getBitmask("NETWORK_TYPE_BITMASK_EDGE") }, - "UMTS" to lazy { getBitmask("NETWORK_TYPE_BITMASK_UMTS") }, - "CDMA" to lazy { getBitmask("NETWORK_TYPE_BITMASK_CDMA") }, - "EVDO_0" to lazy { getBitmask("NETWORK_TYPE_BITMASK_EVDO_0") }, - "EVDO_A" to lazy { getBitmask("NETWORK_TYPE_BITMASK_EVDO_A") }, - "1xRTT" to lazy { getBitmask("NETWORK_TYPE_BITMASK_1xRTT") }, - "HSDPA" to lazy { getBitmask("NETWORK_TYPE_BITMASK_HSDPA") }, - "HSUPA" to lazy { getBitmask("NETWORK_TYPE_BITMASK_HSUPA") }, - "HSPA" to lazy { getBitmask("NETWORK_TYPE_BITMASK_HSPA") }, - "EVDO_B" to lazy { getBitmask("NETWORK_TYPE_BITMASK_EVDO_B") }, - "LTE" to lazy { getBitmask("NETWORK_TYPE_BITMASK_LTE") }, - "EHRPD" to lazy { getBitmask("NETWORK_TYPE_BITMASK_EHRPD") }, - "HSPAP" to lazy { getBitmask("NETWORK_TYPE_BITMASK_HSPAP") }, - "TD_SCDMA" to lazy { getBitmask("NETWORK_TYPE_BITMASK_TD_SCDMA") }, - "LTE_CA" to lazy { getBitmask("NETWORK_TYPE_BITMASK_LTE_CA") }, - "IWLAN" to lazy { getBitmask("NETWORK_TYPE_BITMASK_IWLAN") }, - "NR" to lazy { getBitmask("NETWORK_TYPE_BITMASK_NR") } - ) - - private fun getMask(key: String) = bitmasks[key]?.value ?: 0L - - private fun get2GBitmask(): Long { - return getMask("GSM") or getMask("GPRS") or getMask("EDGE") or getMask("CDMA") or getMask("1xRTT") - } - - private fun get3GBitmask(): Long { - return getMask("EVDO_0") or getMask("EVDO_A") or getMask("EVDO_B") or getMask("EHRPD") or - getMask("HSUPA") or getMask("HSDPA") or getMask("HSPA") or getMask("HSPAP") or - getMask("UMTS") or getMask("TD_SCDMA") - } - - private fun get4GBitmask(): Long { - return getMask("LTE") or getMask("LTE_CA") or getMask("IWLAN") - } - - private fun get5GBitmask(): Long { - return getMask("NR") - } - + + private val reasonUser by lazy { + getStaticIntField("android.telephony.TelephonyManager", "ALLOWED_NETWORK_TYPES_REASON_USER", 0) + } + + private val BITMASK_GSM by lazy { + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_GSM", 1L shl 15) or + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_GPRS", 1L shl 0) or + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_EDGE", 1L shl 1) + } + private val BITMASK_WCDMA by lazy { + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_UMTS", 1L shl 2) or + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_HSDPA", 1L shl 7) or + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_HSUPA", 1L shl 8) or + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_HSPA", 1L shl 9) or + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_HSPAP", 1L shl 14) + } + private val BITMASK_CDMA by lazy { + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_CDMA", 1L shl 3) or + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_1xRTT", 1L shl 6) + } + private val BITMASK_EVDO by lazy { + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_EVDO_0", 1L shl 4) or + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_EVDO_A", 1L shl 5) or + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_EVDO_B", 1L shl 11) + } + private val BITMASK_LTE by lazy { + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_LTE", 1L shl 12) or + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_LTE_CA", 1L shl 18) + } + private val BITMASK_TDSCDMA by lazy { + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_TD_SCDMA", 1L shl 16) + } + private val BITMASK_NR by lazy { + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_NR", 1L shl 19) + } + private fun mapNetworkModeToBitmask(networkMode: Int): Long { return when (networkMode) { - 0 -> get2GBitmask() or get3GBitmask() - 1 -> getMask("GSM") - 2 -> getMask("UMTS") - 3 -> get2GBitmask() or get3GBitmask() - 4 -> getMask("CDMA") or getMask("EVDO_0") or getMask("EVDO_A") or getMask("EVDO_B") - 5 -> getMask("CDMA") - 6 -> getMask("EVDO_0") or getMask("EVDO_A") or getMask("EVDO_B") - 7 -> get2GBitmask() or get3GBitmask() or get4GBitmask() - 8 -> getMask("LTE") or getMask("CDMA") or getMask("EVDO_0") or getMask("EVDO_A") or getMask("EVDO_B") - 9 -> getMask("LTE") or get2GBitmask() or get3GBitmask() - 10 -> getMask("LTE") or getMask("CDMA") or getMask("EVDO_0") or getMask("EVDO_A") or getMask("EVDO_B") or get2GBitmask() or get3GBitmask() - 11 -> getMask("LTE") - 12 -> getMask("LTE") or get3GBitmask() - 13 -> getMask("TD_SCDMA") - 14 -> getMask("TD_SCDMA") or getMask("UMTS") - 15 -> getMask("LTE") or getMask("TD_SCDMA") - 16 -> getMask("TD_SCDMA") or get2GBitmask() - 17 -> getMask("LTE") or getMask("TD_SCDMA") or get2GBitmask() - 18 -> getMask("TD_SCDMA") or get2GBitmask() or get3GBitmask() - 19 -> getMask("LTE") or getMask("TD_SCDMA") or get3GBitmask() - 20 -> getMask("LTE") or getMask("TD_SCDMA") or get2GBitmask() or get3GBitmask() - 21 -> getMask("TD_SCDMA") or getMask("CDMA") or getMask("EVDO_0") or getMask("EVDO_A") or getMask("EVDO_B") or get2GBitmask() or get3GBitmask() - 22 -> getMask("LTE") or getMask("TD_SCDMA") or getMask("CDMA") or getMask("EVDO_0") or getMask("EVDO_A") or getMask("EVDO_B") or get2GBitmask() or get3GBitmask() - 23 -> getMask("NR") - 24 -> getMask("NR") or getMask("LTE") - 25 -> getMask("NR") or getMask("LTE") or getMask("CDMA") or getMask("EVDO_0") or getMask("EVDO_A") or getMask("EVDO_B") - 26 -> getMask("NR") or getMask("LTE") or get2GBitmask() or get3GBitmask() - 27 -> getMask("NR") or getMask("LTE") or getMask("CDMA") or getMask("EVDO_0") or getMask("EVDO_A") or getMask("EVDO_B") or get2GBitmask() or get3GBitmask() - 28 -> getMask("NR") or getMask("LTE") or get3GBitmask() - 29 -> getMask("NR") or getMask("LTE") or getMask("TD_SCDMA") - 30 -> getMask("NR") or getMask("LTE") or getMask("TD_SCDMA") or get2GBitmask() - 31 -> getMask("NR") or getMask("LTE") or getMask("TD_SCDMA") or get3GBitmask() - 32 -> getMask("NR") or getMask("LTE") or getMask("TD_SCDMA") or get2GBitmask() or get3GBitmask() - 33 -> getMask("NR") or getMask("LTE") or getMask("TD_SCDMA") or getMask("CDMA") or getMask("EVDO_0") or getMask("EVDO_A") or getMask("EVDO_B") or get2GBitmask() or get3GBitmask() - else -> get2GBitmask() or get3GBitmask() or get4GBitmask() + 0 -> BITMASK_GSM or BITMASK_WCDMA + 1 -> BITMASK_GSM + 2 -> BITMASK_WCDMA + 3 -> BITMASK_GSM or BITMASK_WCDMA + 4 -> BITMASK_CDMA or BITMASK_EVDO + 5 -> BITMASK_CDMA + 6 -> BITMASK_EVDO + 7 -> BITMASK_GSM or BITMASK_WCDMA or BITMASK_CDMA or BITMASK_EVDO + 8 -> BITMASK_LTE or BITMASK_CDMA or BITMASK_EVDO + 9 -> BITMASK_LTE or BITMASK_GSM or BITMASK_WCDMA + 10 -> BITMASK_LTE or BITMASK_CDMA or BITMASK_EVDO or BITMASK_GSM or BITMASK_WCDMA + 11 -> BITMASK_LTE + 12 -> BITMASK_LTE or BITMASK_WCDMA + 13 -> BITMASK_TDSCDMA + 14 -> BITMASK_TDSCDMA or BITMASK_WCDMA + 15 -> BITMASK_LTE or BITMASK_TDSCDMA + 16 -> BITMASK_TDSCDMA or BITMASK_GSM + 17 -> BITMASK_LTE or BITMASK_TDSCDMA or BITMASK_GSM + 18 -> BITMASK_TDSCDMA or BITMASK_GSM or BITMASK_WCDMA + 19 -> BITMASK_LTE or BITMASK_TDSCDMA or BITMASK_WCDMA + 20 -> BITMASK_LTE or BITMASK_TDSCDMA or BITMASK_GSM or BITMASK_WCDMA + 21 -> BITMASK_TDSCDMA or BITMASK_CDMA or BITMASK_EVDO or BITMASK_GSM or BITMASK_WCDMA + 22 -> BITMASK_LTE or BITMASK_TDSCDMA or BITMASK_CDMA or BITMASK_EVDO or BITMASK_GSM or BITMASK_WCDMA + 23 -> BITMASK_NR + 24 -> BITMASK_NR or BITMASK_LTE + 25 -> BITMASK_NR or BITMASK_LTE or BITMASK_CDMA or BITMASK_EVDO + 26 -> BITMASK_NR or BITMASK_LTE or BITMASK_GSM or BITMASK_WCDMA + 27 -> BITMASK_NR or BITMASK_LTE or BITMASK_CDMA or BITMASK_EVDO or BITMASK_GSM or BITMASK_WCDMA + 28 -> BITMASK_NR or BITMASK_LTE or BITMASK_WCDMA + 29 -> BITMASK_NR or BITMASK_LTE or BITMASK_TDSCDMA + 30 -> BITMASK_NR or BITMASK_LTE or BITMASK_TDSCDMA or BITMASK_GSM + 31 -> BITMASK_NR or BITMASK_LTE or BITMASK_TDSCDMA or BITMASK_WCDMA + 32 -> BITMASK_NR or BITMASK_LTE or BITMASK_TDSCDMA or BITMASK_GSM or BITMASK_WCDMA + 33 -> BITMASK_NR or BITMASK_LTE or BITMASK_TDSCDMA or BITMASK_CDMA or BITMASK_EVDO or BITMASK_GSM or BITMASK_WCDMA + else -> (1L shl 31) - 1 // All } } - + private fun mapBitmaskToNetworkMode(bitmask: Long): Int { - for (mode in 0..33) { - if (bitmask == mapNetworkModeToBitmask(mode)) { - return mode - } + // Check exact matches for 0-33 + for (i in 0..33) { + if (mapNetworkModeToBitmask(i) == bitmask) return i } - return when { - bitmask == getMask("NR") -> 23 - bitmask == getMask("LTE") -> 11 - bitmask == getMask("GSM") -> 1 - bitmask == getMask("UMTS") -> 2 - bitmask == getMask("TD_SCDMA") -> 13 - bitmask == (getMask("NR") or getMask("LTE")) -> 24 - (bitmask and getMask("NR")) != 0L -> 23 - (bitmask and getMask("LTE")) != 0L -> 11 - (bitmask and get3GBitmask()) != 0L -> 2 - (bitmask and get2GBitmask()) != 0L -> 1 - else -> 0 - } + // Handle cases where bitmask might have additional bits + if ((bitmask and BITMASK_NR) != 0L && (bitmask and BITMASK_LTE) != 0L) return 24 + if ((bitmask and BITMASK_NR) != 0L) return 23 + if ((bitmask and BITMASK_LTE) != 0L) return 11 + + return 0 } } override fun onBind(intent: Intent) = object : IRootController.Stub() { - + override fun compatibilityCheck(subId: Int): Boolean { - return try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - reasonUser - iTelephony.setAllowedNetworkTypesForReason( - subId, - reasonUser, - iTelephony.getAllowedNetworkTypesForReason(subId, reasonUser) - ) - } else { - iTelephony.setPreferredNetworkType( - subId, - iTelephony.getPreferredNetworkType(subId) - ) - } - true - } catch (_: Exception) { - false - } + return getITelephony() != null } override fun getCurrentNetworkMode(subId: Int): Int { - return try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val currentBitmask = iTelephony.getAllowedNetworkTypesForReason(subId, reasonUser) - mapBitmaskToNetworkMode(currentBitmask) - } else { - iTelephony.getPreferredNetworkType(subId) + try { + val iTelephony = getITelephony() ?: return -1 + val methods = iTelephony.javaClass.methods + + // Try getAllowedNetworkTypesForReason (Android 11+) + for (m in methods) { + if (m.name == "getAllowedNetworkTypesForReason") { + try { + val bitmask = if (m.parameterCount == 2) { + m.invoke(iTelephony, subId, reasonUser) as Long + } else if (m.parameterCount == 3 && m.parameterTypes[2] == String::class.java) { + m.invoke(iTelephony, subId, reasonUser, "com.android.phone") as Long + } else { + continue + } + + val mode = mapBitmaskToNetworkMode(bitmask) + Log.i(TAG, "Root: getCurrentNetworkMode(subId=$subId) via getAllowedNetworkTypesForReason -> $mode (bitmask=$bitmask)") + return mode + } catch (_: Exception) { + // Try next + } + } } - } catch (_: Exception) { - -1 + + // Fallback: Try getPreferredNetworkType (Android < 11) + for (m in methods) { + if (m.name == "getPreferredNetworkType") { + try { + val mode = if (m.parameterCount == 1) { + m.invoke(iTelephony, subId) as Int + } else if (m.parameterCount == 2 && m.parameterTypes[1] == String::class.java) { + m.invoke(iTelephony, subId, "com.android.phone") as Int + } else { + continue + } + Log.i(TAG, "Root: getCurrentNetworkMode(subId=$subId) via getPreferredNetworkType -> $mode") + return mode + } catch (_: Exception) { + // Try next + } + } + } + } catch (e: Throwable) { + Log.e(TAG, "Root: Error in getCurrentNetworkMode", e) } + return -1 } override fun setNetworkMode(subId: Int, networkMode: Int) { try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val networkTypeBitmask = mapNetworkModeToBitmask(networkMode) - iTelephony.setAllowedNetworkTypesForReason(subId, reasonUser, networkTypeBitmask) - } else { - iTelephony.setPreferredNetworkType(subId, networkMode) + val iTelephony = getITelephony() ?: return + val bitmask = mapNetworkModeToBitmask(networkMode) + val methods = iTelephony.javaClass.methods + + var success = false + + // Try setAllowedNetworkTypesForReason + for (m in methods) { + if (m.name == "setAllowedNetworkTypesForReason") { + try { + if (m.parameterCount == 3) { + m.invoke(iTelephony, subId, reasonUser, bitmask) + success = true + } else if (m.parameterCount == 4 && m.parameterTypes[3] == String::class.java) { + m.invoke(iTelephony, subId, reasonUser, bitmask, "com.android.phone") + success = true + } + if (success) { + Log.i(TAG, "Root: setAllowedNetworkTypesForReason(subId=$subId, mode=$networkMode) success") + break + } + } catch (_: Exception) { + // Try next + } + } } - } catch (_: Exception) { - // fail + + if (!success) { + for (m in methods) { + if (m.name == "setAllowedNetworkTypes") { + try { + if (m.parameterCount == 2) { + m.invoke(iTelephony, subId, bitmask) + success = true + } else if (m.parameterCount == 3 && m.parameterTypes[1].name == "long") { + m.invoke(iTelephony, subId, bitmask, "com.android.phone") + success = true + } + if (success) { + Log.i(TAG, "Root: setAllowedNetworkTypes(subId=$subId, bitmask=$bitmask) success") + break + } + } catch (_: Exception) { + // Try next + } + } + } + } + + if (!success) { + for (m in methods) { + if (m.name == "setPreferredNetworkType" && m.parameterCount == 2) { + try { + m.invoke(iTelephony, subId, networkMode) + success = true + Log.i(TAG, "Root: setPreferredNetworkType(subId=$subId, mode=$networkMode) success") + break + } catch (_: Exception) { + // Try next + } + } + } + } + + if (!success) { + Log.w(TAG, "Root: Failed to set network mode $networkMode for subId $subId") + } + + } catch (e: Throwable) { + Log.e(TAG, "Root: FATAL error in setNetworkMode", e) } } } diff --git a/app/src/main/java/com/supernova/networkswitch/service/ShizukuControllerService.kt b/app/src/main/java/com/supernova/networkswitch/service/ShizukuControllerService.kt index 1408b64..4cd85ee 100644 --- a/app/src/main/java/com/supernova/networkswitch/service/ShizukuControllerService.kt +++ b/app/src/main/java/com/supernova/networkswitch/service/ShizukuControllerService.kt @@ -1,308 +1,288 @@ package com.supernova.networkswitch.service -import android.annotation.SuppressLint import android.content.Context import android.os.Build +import android.os.IBinder import android.os.ServiceManager +import android.util.Log import androidx.annotation.Keep -import com.android.internal.telephony.ITelephony import com.supernova.networkswitch.IShizukuController +import org.lsposed.hiddenapibypass.HiddenApiBypass -/** - * Simple Shizuku service for network control operations - */ class ShizukuControllerService() : IShizukuController.Stub() { - companion object { - private val iTelephony by lazy { - ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE)) - } - - private val reasonUser by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("ALLOWED_NETWORK_TYPES_REASON_USER") - .getInt(null) - } - - // Get network type bitmasks from Android TelephonyManager constants - private val NETWORK_TYPE_BITMASK_GSM by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("NETWORK_TYPE_BITMASK_GSM") - .getLong(null) - } - - private val NETWORK_TYPE_BITMASK_GPRS by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("NETWORK_TYPE_BITMASK_GPRS") - .getLong(null) - } - - private val NETWORK_TYPE_BITMASK_EDGE by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("NETWORK_TYPE_BITMASK_EDGE") - .getLong(null) - } - - private val NETWORK_TYPE_BITMASK_UMTS by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("NETWORK_TYPE_BITMASK_UMTS") - .getLong(null) - } - - private val NETWORK_TYPE_BITMASK_CDMA by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("NETWORK_TYPE_BITMASK_CDMA") - .getLong(null) - } - - private val NETWORK_TYPE_BITMASK_EVDO_0 by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("NETWORK_TYPE_BITMASK_EVDO_0") - .getLong(null) - } - - private val NETWORK_TYPE_BITMASK_EVDO_A by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("NETWORK_TYPE_BITMASK_EVDO_A") - .getLong(null) - } - - private val NETWORK_TYPE_BITMASK_1xRTT by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("NETWORK_TYPE_BITMASK_1xRTT") - .getLong(null) - } - - private val NETWORK_TYPE_BITMASK_HSDPA by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("NETWORK_TYPE_BITMASK_HSDPA") - .getLong(null) - } - - private val NETWORK_TYPE_BITMASK_HSUPA by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("NETWORK_TYPE_BITMASK_HSUPA") - .getLong(null) - } - - private val NETWORK_TYPE_BITMASK_HSPA by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("NETWORK_TYPE_BITMASK_HSPA") - .getLong(null) - } - - private val NETWORK_TYPE_BITMASK_EVDO_B by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("NETWORK_TYPE_BITMASK_EVDO_B") - .getLong(null) - } - - private val NETWORK_TYPE_BITMASK_LTE by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("NETWORK_TYPE_BITMASK_LTE") - .getLong(null) - } - - private val NETWORK_TYPE_BITMASK_EHRPD by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("NETWORK_TYPE_BITMASK_EHRPD") - .getLong(null) - } - - private val NETWORK_TYPE_BITMASK_HSPAP by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("NETWORK_TYPE_BITMASK_HSPAP") - .getLong(null) - } - - private val NETWORK_TYPE_BITMASK_TD_SCDMA by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("NETWORK_TYPE_BITMASK_TD_SCDMA") - .getLong(null) - } - - private val NETWORK_TYPE_BITMASK_LTE_CA by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("NETWORK_TYPE_BITMASK_LTE_CA") - .getLong(null) - } - - private val NETWORK_TYPE_BITMASK_IWLAN by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("NETWORK_TYPE_BITMASK_IWLAN") - .getLong(null) - } - - private val NETWORK_TYPE_BITMASK_NR by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("NETWORK_TYPE_BITMASK_NR") - .getLong(null) - } - - // Combined bitmasks for common network classes - private fun get2GBitmask(): Long { - return try { - NETWORK_TYPE_BITMASK_GSM or NETWORK_TYPE_BITMASK_GPRS or - NETWORK_TYPE_BITMASK_EDGE or NETWORK_TYPE_BITMASK_CDMA or NETWORK_TYPE_BITMASK_1xRTT - } catch (e: Exception) { - 1L or 2L or 4L or 16L or 128L + init { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + try { + HiddenApiBypass.setHiddenApiExemptions("Landroid/telephony/", "Lcom/android/internal/telephony/") + } catch (e: Throwable) { + Log.e("NetworkSwitch", "Shizuku: HiddenApiBypass failed", e) } } - - private fun get3GBitmask(): Long { + } + + @Keep + constructor(context: Context) : this() + + companion object { + private const val TAG = "NetworkSwitch" + + private fun getITelephony(): Any? { return try { - NETWORK_TYPE_BITMASK_EVDO_0 or NETWORK_TYPE_BITMASK_EVDO_A or - NETWORK_TYPE_BITMASK_EVDO_B or NETWORK_TYPE_BITMASK_EHRPD or - NETWORK_TYPE_BITMASK_HSUPA or NETWORK_TYPE_BITMASK_HSDPA or - NETWORK_TYPE_BITMASK_HSPA or NETWORK_TYPE_BITMASK_HSPAP or - NETWORK_TYPE_BITMASK_UMTS or NETWORK_TYPE_BITMASK_TD_SCDMA + val binder = ServiceManager.getService(Context.TELEPHONY_SERVICE) + val stubClass = Class.forName("com.android.internal.telephony.ITelephony\$Stub") + val asInterfaceMethod = stubClass.getMethod("asInterface", IBinder::class.java) + asInterfaceMethod.invoke(null, binder) } catch (e: Exception) { - 32L or 64L or 2048L or 8192L or 512L or 256L or 1024L or 16384L or 8L or 32768L + Log.e(TAG, "Shizuku: Failed to get ITelephony", e) + null } } - - private fun get4GBitmask(): Long { + + private fun getStaticLongField(className: String, fieldName: String, fallback: Long): Long { return try { - NETWORK_TYPE_BITMASK_LTE or NETWORK_TYPE_BITMASK_LTE_CA or NETWORK_TYPE_BITMASK_IWLAN - } catch (e: Exception) { - 4096L or 65536L or 131072L + val clazz = Class.forName(className) + val field = clazz.getDeclaredField(fieldName) + field.isAccessible = true + field.getLong(null) + } catch (_: Exception) { + fallback } } - - private fun get5GBitmask(): Long { + + private fun getStaticIntField(className: String, fieldName: String, fallback: Int): Int { return try { - NETWORK_TYPE_BITMASK_NR - } catch (e: Exception) { - 524288L + val clazz = Class.forName(className) + val field = clazz.getDeclaredField(fieldName) + field.isAccessible = true + field.getInt(null) + } catch (_: Exception) { + fallback } } - - /** - * Map RIL network mode constants to network type bitmasks for Android 12+ - */ + + private val reasonUser by lazy { + getStaticIntField("android.telephony.TelephonyManager", "ALLOWED_NETWORK_TYPES_REASON_USER", 0) + } + + private val BITMASK_GSM by lazy { + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_GSM", 1L shl 15) or + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_GPRS", 1L shl 0) or + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_EDGE", 1L shl 1) + } + private val BITMASK_WCDMA by lazy { + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_UMTS", 1L shl 2) or + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_HSDPA", 1L shl 7) or + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_HSUPA", 1L shl 8) or + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_HSPA", 1L shl 9) or + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_HSPAP", 1L shl 14) + } + private val BITMASK_CDMA by lazy { + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_CDMA", 1L shl 3) or + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_1xRTT", 1L shl 6) + } + private val BITMASK_EVDO by lazy { + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_EVDO_0", 1L shl 4) or + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_EVDO_A", 1L shl 5) or + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_EVDO_B", 1L shl 11) + } + private val BITMASK_LTE by lazy { + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_LTE", 1L shl 12) or + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_LTE_CA", 1L shl 18) + } + private val BITMASK_TDSCDMA by lazy { + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_TD_SCDMA", 1L shl 16) + } + private val BITMASK_NR by lazy { + getStaticLongField("android.telephony.TelephonyManager", "NETWORK_TYPE_BITMASK_NR", 1L shl 19) + } + private fun mapNetworkModeToBitmask(networkMode: Int): Long { return when (networkMode) { - 0 -> get2GBitmask() or get3GBitmask() // WCDMA_PREF - 1 -> NETWORK_TYPE_BITMASK_GSM // GSM_ONLY - 2 -> NETWORK_TYPE_BITMASK_UMTS // WCDMA_ONLY - 3 -> get2GBitmask() or get3GBitmask() // GSM_UMTS - 4 -> NETWORK_TYPE_BITMASK_CDMA or NETWORK_TYPE_BITMASK_EVDO_0 or NETWORK_TYPE_BITMASK_EVDO_A or NETWORK_TYPE_BITMASK_EVDO_B // CDMA - 5 -> NETWORK_TYPE_BITMASK_CDMA // CDMA_NO_EVDO - 6 -> NETWORK_TYPE_BITMASK_EVDO_0 or NETWORK_TYPE_BITMASK_EVDO_A or NETWORK_TYPE_BITMASK_EVDO_B // EVDO_NO_CDMA - 7 -> get2GBitmask() or get3GBitmask() or get4GBitmask() // GLOBAL - 8 -> NETWORK_TYPE_BITMASK_LTE or NETWORK_TYPE_BITMASK_CDMA or NETWORK_TYPE_BITMASK_EVDO_0 or NETWORK_TYPE_BITMASK_EVDO_A or NETWORK_TYPE_BITMASK_EVDO_B // LTE_CDMA_EVDO - 9 -> NETWORK_TYPE_BITMASK_LTE or get2GBitmask() or get3GBitmask() // LTE_GSM_WCDMA - 10 -> NETWORK_TYPE_BITMASK_LTE or NETWORK_TYPE_BITMASK_CDMA or NETWORK_TYPE_BITMASK_EVDO_0 or NETWORK_TYPE_BITMASK_EVDO_A or NETWORK_TYPE_BITMASK_EVDO_B or get2GBitmask() or get3GBitmask() // LTE_CDMA_EVDO_GSM_WCDMA - 11 -> NETWORK_TYPE_BITMASK_LTE // LTE_ONLY - 12 -> NETWORK_TYPE_BITMASK_LTE or get3GBitmask() // LTE_WCDMA - 13 -> NETWORK_TYPE_BITMASK_TD_SCDMA // TDSCDMA_ONLY - 14 -> NETWORK_TYPE_BITMASK_TD_SCDMA or NETWORK_TYPE_BITMASK_UMTS // TDSCDMA_WCDMA - 15 -> NETWORK_TYPE_BITMASK_LTE or NETWORK_TYPE_BITMASK_TD_SCDMA // LTE_TDSCDMA - 16 -> NETWORK_TYPE_BITMASK_TD_SCDMA or get2GBitmask() // TDSCDMA_GSM - 17 -> NETWORK_TYPE_BITMASK_LTE or NETWORK_TYPE_BITMASK_TD_SCDMA or get2GBitmask() // LTE_TDSCDMA_GSM - 18 -> NETWORK_TYPE_BITMASK_TD_SCDMA or get2GBitmask() or get3GBitmask() // TDSCDMA_GSM_WCDMA - 19 -> NETWORK_TYPE_BITMASK_LTE or NETWORK_TYPE_BITMASK_TD_SCDMA or get3GBitmask() // LTE_TDSCDMA_WCDMA - 20 -> NETWORK_TYPE_BITMASK_LTE or NETWORK_TYPE_BITMASK_TD_SCDMA or get2GBitmask() or get3GBitmask() // LTE_TDSCDMA_GSM_WCDMA - 21 -> NETWORK_TYPE_BITMASK_TD_SCDMA or NETWORK_TYPE_BITMASK_CDMA or NETWORK_TYPE_BITMASK_EVDO_0 or NETWORK_TYPE_BITMASK_EVDO_A or NETWORK_TYPE_BITMASK_EVDO_B or get2GBitmask() or get3GBitmask() // TDSCDMA_CDMA_EVDO_GSM_WCDMA - 22 -> NETWORK_TYPE_BITMASK_LTE or NETWORK_TYPE_BITMASK_TD_SCDMA or NETWORK_TYPE_BITMASK_CDMA or NETWORK_TYPE_BITMASK_EVDO_0 or NETWORK_TYPE_BITMASK_EVDO_A or NETWORK_TYPE_BITMASK_EVDO_B or get2GBitmask() or get3GBitmask() // LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA - 23 -> NETWORK_TYPE_BITMASK_NR // NR_ONLY - 24 -> NETWORK_TYPE_BITMASK_NR or NETWORK_TYPE_BITMASK_LTE // NR_LTE - 25 -> NETWORK_TYPE_BITMASK_NR or NETWORK_TYPE_BITMASK_LTE or NETWORK_TYPE_BITMASK_CDMA or NETWORK_TYPE_BITMASK_EVDO_0 or NETWORK_TYPE_BITMASK_EVDO_A or NETWORK_TYPE_BITMASK_EVDO_B // NR_LTE_CDMA_EVDO - 26 -> NETWORK_TYPE_BITMASK_NR or NETWORK_TYPE_BITMASK_LTE or get2GBitmask() or get3GBitmask() // NR_LTE_GSM_WCDMA - 27 -> NETWORK_TYPE_BITMASK_NR or NETWORK_TYPE_BITMASK_LTE or NETWORK_TYPE_BITMASK_CDMA or NETWORK_TYPE_BITMASK_EVDO_0 or NETWORK_TYPE_BITMASK_EVDO_A or NETWORK_TYPE_BITMASK_EVDO_B or get2GBitmask() or get3GBitmask() // NR_LTE_CDMA_EVDO_GSM_WCDMA - 28 -> NETWORK_TYPE_BITMASK_NR or NETWORK_TYPE_BITMASK_LTE or get3GBitmask() // NR_LTE_WCDMA - 29 -> NETWORK_TYPE_BITMASK_NR or NETWORK_TYPE_BITMASK_LTE or NETWORK_TYPE_BITMASK_TD_SCDMA // NR_LTE_TDSCDMA - 30 -> NETWORK_TYPE_BITMASK_NR or NETWORK_TYPE_BITMASK_LTE or NETWORK_TYPE_BITMASK_TD_SCDMA or get2GBitmask() // NR_LTE_TDSCDMA_GSM - 31 -> NETWORK_TYPE_BITMASK_NR or NETWORK_TYPE_BITMASK_LTE or NETWORK_TYPE_BITMASK_TD_SCDMA or get3GBitmask() // NR_LTE_TDSCDMA_WCDMA - 32 -> NETWORK_TYPE_BITMASK_NR or NETWORK_TYPE_BITMASK_LTE or NETWORK_TYPE_BITMASK_TD_SCDMA or get2GBitmask() or get3GBitmask() // NR_LTE_TDSCDMA_GSM_WCDMA - 33 -> NETWORK_TYPE_BITMASK_NR or NETWORK_TYPE_BITMASK_LTE or NETWORK_TYPE_BITMASK_TD_SCDMA or NETWORK_TYPE_BITMASK_CDMA or NETWORK_TYPE_BITMASK_EVDO_0 or NETWORK_TYPE_BITMASK_EVDO_A or NETWORK_TYPE_BITMASK_EVDO_B or get2GBitmask() or get3GBitmask() // NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA - else -> get2GBitmask() or get3GBitmask() or get4GBitmask() // Default fallback + 0 -> BITMASK_GSM or BITMASK_WCDMA + 1 -> BITMASK_GSM + 2 -> BITMASK_WCDMA + 3 -> BITMASK_GSM or BITMASK_WCDMA + 4 -> BITMASK_CDMA or BITMASK_EVDO + 5 -> BITMASK_CDMA + 6 -> BITMASK_EVDO + 7 -> BITMASK_GSM or BITMASK_WCDMA or BITMASK_CDMA or BITMASK_EVDO + 8 -> BITMASK_LTE or BITMASK_CDMA or BITMASK_EVDO + 9 -> BITMASK_LTE or BITMASK_GSM or BITMASK_WCDMA + 10 -> BITMASK_LTE or BITMASK_CDMA or BITMASK_EVDO or BITMASK_GSM or BITMASK_WCDMA + 11 -> BITMASK_LTE + 12 -> BITMASK_LTE or BITMASK_WCDMA + 13 -> BITMASK_TDSCDMA + 14 -> BITMASK_TDSCDMA or BITMASK_WCDMA + 15 -> BITMASK_LTE or BITMASK_TDSCDMA + 16 -> BITMASK_TDSCDMA or BITMASK_GSM + 17 -> BITMASK_LTE or BITMASK_TDSCDMA or BITMASK_GSM + 18 -> BITMASK_TDSCDMA or BITMASK_GSM or BITMASK_WCDMA + 19 -> BITMASK_LTE or BITMASK_TDSCDMA or BITMASK_WCDMA + 20 -> BITMASK_LTE or BITMASK_TDSCDMA or BITMASK_GSM or BITMASK_WCDMA + 21 -> BITMASK_TDSCDMA or BITMASK_CDMA or BITMASK_EVDO or BITMASK_GSM or BITMASK_WCDMA + 22 -> BITMASK_LTE or BITMASK_TDSCDMA or BITMASK_CDMA or BITMASK_EVDO or BITMASK_GSM or BITMASK_WCDMA + 23 -> BITMASK_NR + 24 -> BITMASK_NR or BITMASK_LTE + 25 -> BITMASK_NR or BITMASK_LTE or BITMASK_CDMA or BITMASK_EVDO + 26 -> BITMASK_NR or BITMASK_LTE or BITMASK_GSM or BITMASK_WCDMA + 27 -> BITMASK_NR or BITMASK_LTE or BITMASK_CDMA or BITMASK_EVDO or BITMASK_GSM or BITMASK_WCDMA + 28 -> BITMASK_NR or BITMASK_LTE or BITMASK_WCDMA + 29 -> BITMASK_NR or BITMASK_LTE or BITMASK_TDSCDMA + 30 -> BITMASK_NR or BITMASK_LTE or BITMASK_TDSCDMA or BITMASK_GSM + 31 -> BITMASK_NR or BITMASK_LTE or BITMASK_TDSCDMA or BITMASK_WCDMA + 32 -> BITMASK_NR or BITMASK_LTE or BITMASK_TDSCDMA or BITMASK_GSM or BITMASK_WCDMA + 33 -> BITMASK_NR or BITMASK_LTE or BITMASK_TDSCDMA or BITMASK_CDMA or BITMASK_EVDO or BITMASK_GSM or BITMASK_WCDMA + else -> (1L shl 31) - 1 // All } } - - /** - * Map bitmask back to RIL network mode for getCurrentNetworkMode - */ + private fun mapBitmaskToNetworkMode(bitmask: Long): Int { - // Try exact matches for all modes by comparing with their expected bitmasks - for (mode in 0..33) { - try { - if (bitmask == mapNetworkModeToBitmask(mode)) { - return mode - } - } catch (_: Exception) { - // Skip invalid modes - } + // Check exact matches for 0-33 + for (i in 0..33) { + if (mapNetworkModeToBitmask(i) == bitmask) return i } - // If no exact match found, use simple fallback logic - return when { - bitmask == NETWORK_TYPE_BITMASK_NR -> 23 // NR_ONLY - bitmask == NETWORK_TYPE_BITMASK_LTE -> 11 // LTE_ONLY - bitmask == NETWORK_TYPE_BITMASK_GSM -> 1 // GSM_ONLY - bitmask == NETWORK_TYPE_BITMASK_UMTS -> 2 // WCDMA_ONLY - bitmask == NETWORK_TYPE_BITMASK_TD_SCDMA -> 13 // TDSCDMA_ONLY - bitmask == (NETWORK_TYPE_BITMASK_NR or NETWORK_TYPE_BITMASK_LTE) -> 24 // NR_LTE - (bitmask and NETWORK_TYPE_BITMASK_NR) != 0L -> 23 // Has 5G, default to NR_ONLY - (bitmask and NETWORK_TYPE_BITMASK_LTE) != 0L -> 11 // Has LTE, default to LTE_ONLY - (bitmask and get3GBitmask()) != 0L -> 2 // Has 3G, default to WCDMA_ONLY - (bitmask and get2GBitmask()) != 0L -> 1 // Has 2G, default to GSM_ONLY - else -> 0 // WCDMA_PREF as ultimate fallback - } + // Handle cases where bitmask might have additional bits (like LTE_CA vs LTE) + if ((bitmask and BITMASK_NR) != 0L && (bitmask and BITMASK_LTE) != 0L) return 24 + if ((bitmask and BITMASK_NR) != 0L) return 23 + if ((bitmask and BITMASK_LTE) != 0L) return 11 + + return 0 } } - @Keep - constructor(context: Context) : this() - override fun compatibilityCheck(subId: Int): Boolean { - return try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - reasonUser - iTelephony.setAllowedNetworkTypesForReason( - subId, - reasonUser, - iTelephony.getAllowedNetworkTypesForReason(subId, reasonUser) - ) - } else { - iTelephony.setPreferredNetworkType( - subId, - iTelephony.getPreferredNetworkType(subId) - ) - } - true - } catch (_: Exception) { - false - } + return getITelephony() != null } override fun getCurrentNetworkMode(subId: Int): Int { - return try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val currentBitmask = iTelephony.getAllowedNetworkTypesForReason(subId, reasonUser) - mapBitmaskToNetworkMode(currentBitmask) - } else { - iTelephony.getPreferredNetworkType(subId) + try { + val iTelephony = getITelephony() ?: return -1 + val methods = iTelephony.javaClass.methods + + // Try getAllowedNetworkTypesForReason (Android 11+) + for (m in methods) { + if (m.name == "getAllowedNetworkTypesForReason") { + try { + val bitmask = if (m.parameterCount == 2) { + m.invoke(iTelephony, subId, reasonUser) as Long + } else if (m.parameterCount == 3 && m.parameterTypes[2] == String::class.java) { + m.invoke(iTelephony, subId, reasonUser, "com.android.phone") as Long + } else { + continue + } + + val mode = mapBitmaskToNetworkMode(bitmask) + Log.i(TAG, "Shizuku: getCurrentNetworkMode(subId=$subId) via getAllowedNetworkTypesForReason -> $mode (bitmask=$bitmask)") + return mode + } catch (_: Exception) { + // Continue to next method if one fails + } + } } - } catch (_: Exception) { - -1 + + // Fallback: Try getPreferredNetworkType (Android < 11) + for (m in methods) { + if (m.name == "getPreferredNetworkType") { + try { + val mode = if (m.parameterCount == 1) { + m.invoke(iTelephony, subId) as Int + } else if (m.parameterCount == 2 && m.parameterTypes[1] == String::class.java) { + m.invoke(iTelephony, subId, "com.android.phone") as Int + } else { + continue + } + Log.i(TAG, "Shizuku: getCurrentNetworkMode(subId=$subId) via getPreferredNetworkType -> $mode") + return mode + } catch (_: Exception) { + // Try next + } + } + } + } catch (e: Throwable) { + Log.e(TAG, "Shizuku: Error in getCurrentNetworkMode", e) } + return -1 } override fun setNetworkMode(subId: Int, networkMode: Int) { try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val networkTypeBitmask = mapNetworkModeToBitmask(networkMode) - iTelephony.setAllowedNetworkTypesForReason(subId, reasonUser, networkTypeBitmask) - } else { - iTelephony.setPreferredNetworkType(subId, networkMode) + val iTelephony = getITelephony() ?: return + val bitmask = mapNetworkModeToBitmask(networkMode) + val methods = iTelephony.javaClass.methods + + var success = false + + // Try setAllowedNetworkTypesForReason + for (m in methods) { + if (m.name == "setAllowedNetworkTypesForReason") { + try { + if (m.parameterCount == 3) { + m.invoke(iTelephony, subId, reasonUser, bitmask) + success = true + } else if (m.parameterCount == 4 && m.parameterTypes[3] == String::class.java) { + m.invoke(iTelephony, subId, reasonUser, bitmask, "com.android.phone") + success = true + } + if (success) { + Log.i(TAG, "Shizuku: setAllowedNetworkTypesForReason(subId=$subId, mode=$networkMode) success") + break + } + } catch (_: Exception) { + // Try next + } + } } - } catch (_: Exception) { - // Silently fail + + if (!success) { + for (m in methods) { + if (m.name == "setAllowedNetworkTypes") { + try { + if (m.parameterCount == 2) { + m.invoke(iTelephony, subId, bitmask) + success = true + } else if (m.parameterCount == 3 && m.parameterTypes[1].name == "long") { + m.invoke(iTelephony, subId, bitmask, "com.android.phone") + success = true + } + if (success) { + Log.i(TAG, "Shizuku: setAllowedNetworkTypes(subId=$subId, bitmask=$bitmask) success") + break + } + } catch (_: Exception) { + // Try next + } + } + } + } + + if (!success) { + for (m in methods) { + if (m.name == "setPreferredNetworkType" && m.parameterCount == 2) { + try { + m.invoke(iTelephony, subId, networkMode) + success = true + Log.i(TAG, "Shizuku: setPreferredNetworkType(subId=$subId, mode=$networkMode) success") + break + } catch (_: Exception) { + // Try next + } + } + } + } + + if (!success) { + Log.w(TAG, "Shizuku: Failed to set network mode $networkMode for subId $subId") + } + + } catch (e: Throwable) { + Log.e(TAG, "Shizuku: FATAL error in setNetworkMode", e) } } override fun destroy() { - // Cleanup if needed + Log.d(TAG, "ShizukuControllerService: destroy") } } + diff --git a/build.gradle.kts b/build.gradle.kts index 27921a7..98169b7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { alias(libs.plugins.android.application) apply false - alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.compose) apply false alias(libs.plugins.hilt) apply false - alias(libs.plugins.kotlin.kapt) apply false + alias(libs.plugins.legacy.kapt) apply false } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a1b1ffd..54a6d91 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] -agp = "8.7.0" -kotlin = "2.0.21" +agp = "9.3.1" +kotlin = "2.4.10" coreKtx = "1.13.1" junit = "4.13.2" junitVersion = "1.2.1" @@ -12,10 +12,12 @@ libsu = "5.0.4" foundation = "1.7.3" compose = "1.7.3" shizuku = "13.1.5" -hilt = "2.48" +hilt = "2.60.1" datastore = "1.1.1" +lifecycleProcess = "2.8.6" mockk = "1.13.10" coroutinesTest = "1.8.0" +hiddenapibypass = "6.1" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -43,15 +45,17 @@ hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.r hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version = "1.1.0" } androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" } androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleRuntimeKtx" } +androidx-lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "lifecycleProcess" } mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" } mockk-android = { group = "io.mockk", name = "mockk-android", version.ref = "mockk" } hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" } kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutinesTest" } +hiddenapibypass = { group = "org.lsposed.hiddenapibypass", name = "hiddenapibypass", version.ref = "hiddenapibypass" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } -kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } +legacy-kapt = { id = "com.android.legacy-kapt", version.ref = "agp" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37f853b..ca74e71 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,6 @@ +#Sat Aug 08 23:16:04 EDT 2026 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip -networkTimeout=10000 -validateDistributionUrl=true +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/hiddenapi/build.gradle.kts b/hiddenapi/build.gradle.kts index 7baf9f1..add56cc 100644 --- a/hiddenapi/build.gradle.kts +++ b/hiddenapi/build.gradle.kts @@ -4,11 +4,10 @@ plugins { android { namespace = "com.android" - compileSdk = 36 + compileSdk = 35 defaultConfig { minSdk = 24 - targetSdk = 36 } compileOptions { diff --git a/media/Configuration_menu.jpg b/media/Configuration_menu.jpg new file mode 100644 index 0000000..8c10587 Binary files /dev/null and b/media/Configuration_menu.jpg differ