From 899d331d0d53fd236215ce961e88a058d0b3c1d3 Mon Sep 17 00:00:00 2001 From: F0x1d Date: Sun, 14 Jul 2024 16:53:07 +0300 Subject: [PATCH 1/2] [feat]: Shizuku support instead of Root --- app/build.gradle | 6 +- app/src/main/AndroidManifest.xml | 14 +- .../moe/xzr/fivegtile/IFivegController.aidl | 10 +- .../xzr/fivegtile/FivegControllerService.kt | 127 -------- .../java/moe/xzr/fivegtile/MainActivity.kt | 270 ------------------ .../java/moe/xzr/fivegtile/MainViewModel.kt | 54 ---- app/src/main/java/moe/xzr/fivegtile/Utils.kt | 10 - .../repository/ShizukuServiceRepository.kt | 92 ++++++ .../{ => service}/FivegTileService.kt | 36 +-- .../service/ShizukuControllerService.kt | 129 +++++++++ .../ui/activity/main/MainActivity.kt | 47 +++ .../ui/activity/main/MainViewModel.kt | 36 +++ .../ui/activity/main/compose/MainCards.kt | 227 +++++++++++++++ .../ui/activity/main/compose/MainScreen.kt | 64 +++++ app/src/main/res/values-zh-rCN/strings.xml | 4 +- app/src/main/res/values/strings.xml | 6 +- build.gradle | 5 +- gradle/wrapper/gradle-wrapper.properties | 6 +- 18 files changed, 634 insertions(+), 509 deletions(-) delete mode 100644 app/src/main/java/moe/xzr/fivegtile/FivegControllerService.kt delete mode 100644 app/src/main/java/moe/xzr/fivegtile/MainActivity.kt delete mode 100644 app/src/main/java/moe/xzr/fivegtile/MainViewModel.kt delete mode 100644 app/src/main/java/moe/xzr/fivegtile/Utils.kt create mode 100644 app/src/main/java/moe/xzr/fivegtile/repository/ShizukuServiceRepository.kt rename app/src/main/java/moe/xzr/fivegtile/{ => service}/FivegTileService.kt (59%) create mode 100644 app/src/main/java/moe/xzr/fivegtile/service/ShizukuControllerService.kt create mode 100644 app/src/main/java/moe/xzr/fivegtile/ui/activity/main/MainActivity.kt create mode 100644 app/src/main/java/moe/xzr/fivegtile/ui/activity/main/MainViewModel.kt create mode 100644 app/src/main/java/moe/xzr/fivegtile/ui/activity/main/compose/MainCards.kt create mode 100644 app/src/main/java/moe/xzr/fivegtile/ui/activity/main/compose/MainScreen.kt diff --git a/app/build.gradle b/app/build.gradle index fee4778..e1498b3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -54,8 +54,8 @@ dependencies { implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" implementation 'androidx.compose.material3:material3:1.0.1' - implementation "com.github.topjohnwu.libsu:core:${libsuVersion}" - implementation "com.github.topjohnwu.libsu:service:${libsuVersion}" + implementation "dev.rikka.shizuku:api:${shizuku_version}" + implementation "dev.rikka.shizuku:provider:${shizuku_version}" debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" -} \ No newline at end of file +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4a63fd1..dfb1514 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,7 +10,7 @@ android:theme="@style/Theme.FivegTile" tools:targetApi="31"> @@ -25,7 +25,7 @@ android:value="" /> + + - \ No newline at end of file + diff --git a/app/src/main/aidl/moe/xzr/fivegtile/IFivegController.aidl b/app/src/main/aidl/moe/xzr/fivegtile/IFivegController.aidl index 65c5b20..7a393a6 100644 --- a/app/src/main/aidl/moe/xzr/fivegtile/IFivegController.aidl +++ b/app/src/main/aidl/moe/xzr/fivegtile/IFivegController.aidl @@ -1,7 +1,9 @@ package moe.xzr.fivegtile; interface IFivegController { - boolean compatibilityCheck(int subId); - boolean getFivegEnabled(int subId); - void setFivegEnabled(int subId, boolean enabled); -} \ No newline at end of file + boolean compatibilityCheck(int subId) = 2; + boolean getFivegEnabled(int subId) = 3; + void setFivegEnabled(int subId, boolean enabled) = 4; + + void destroy() = 16777114; +} diff --git a/app/src/main/java/moe/xzr/fivegtile/FivegControllerService.kt b/app/src/main/java/moe/xzr/fivegtile/FivegControllerService.kt deleted file mode 100644 index 6b5ff9a..0000000 --- a/app/src/main/java/moe/xzr/fivegtile/FivegControllerService.kt +++ /dev/null @@ -1,127 +0,0 @@ -package moe.xzr.fivegtile - -import android.annotation.SuppressLint -import android.content.Context -import android.content.Intent -import android.os.Build -import android.os.ServiceManager -import com.android.internal.telephony.ITelephony -import com.topjohnwu.superuser.ipc.RootService - -/** - * TelephonyManager is not properly initialized in this context, thus - * everything is done by directly calling telephony service. - */ -class FivegControllerService : RootService() { - 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) - } - private val typeNr by lazy { - Class.forName("android.telephony.TelephonyManager") - .getDeclaredField("NETWORK_TYPE_BITMASK_NR") - .getLong(null) - } - - @delegate:SuppressLint("PrivateApi", "BlockedPrivateApi") - private val modeLte by lazy { - Class.forName("com.android.internal.telephony.RILConstants") - .getDeclaredField("NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA") - .getInt(null) - } - - @delegate:SuppressLint("PrivateApi", "BlockedPrivateApi") - private val modeNr by lazy { - Class.forName("com.android.internal.telephony.RILConstants") - .getDeclaredField("NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA") - .getInt(null) - } - } - - override fun onBind(intent: Intent) = object : IFivegController.Stub() { - override fun compatibilityCheck(subId: Int): Boolean { - return try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - reasonUser - typeNr - iTelephony.setAllowedNetworkTypesForReason( - subId, - reasonUser, - iTelephony.getAllowedNetworkTypesForReason( - subId, - reasonUser - ) - ) - } else { - modeLte - modeNr - // For Q and R. - iTelephony.setPreferredNetworkType( - subId, - iTelephony.getPreferredNetworkType(subId) - ) - } - } catch (_: Exception) { - false - } - } - - override fun getFivegEnabled(subId: Int): Boolean { - return try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - iTelephony.getAllowedNetworkTypesForReason( - subId, - reasonUser - ) and typeNr != 0L - } else { - // For Q and R. - iTelephony.getPreferredNetworkType(subId) == - modeNr - } - } catch (_: Exception) { - false - } - } - - override fun setFivegEnabled(subId: Int, enabled: Boolean) { - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - var curTypes = iTelephony.getAllowedNetworkTypesForReason( - subId, - reasonUser - ) - curTypes = if (enabled) { - curTypes or typeNr - } else { - curTypes and typeNr.inv() - } - iTelephony.setAllowedNetworkTypesForReason( - subId, - reasonUser, - curTypes - ) - } else { - // For Q and R. - if (enabled) { - iTelephony.setPreferredNetworkType( - subId, - modeNr - ) - } else { - iTelephony.setPreferredNetworkType( - subId, - modeLte - ) - } - } - } catch (_: Exception) { - - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/moe/xzr/fivegtile/MainActivity.kt b/app/src/main/java/moe/xzr/fivegtile/MainActivity.kt deleted file mode 100644 index eccd58b..0000000 --- a/app/src/main/java/moe/xzr/fivegtile/MainActivity.kt +++ /dev/null @@ -1,270 +0,0 @@ -package moe.xzr.fivegtile - -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.activity.viewModels -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.Crossfade -import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.* -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import moe.xzr.fivegtile.ui.theme.FivegTileTheme - -class MainActivity : ComponentActivity() { - private val viewModel by viewModels() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - MainComposable(viewModel.getCompatibilityState()) - } - } - - @Composable - private fun MainComposable(compatibilityState: MainViewModel.CompatibilityState) { - FivegTileTheme { - // A surface container using the 'background' color from the theme - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background - ) { - Column { - TopBar() - Column( - modifier = Modifier - .fillMaxWidth() - .verticalScroll(rememberScrollState()) - ) { - CompatibilityHint(compatibilityState = compatibilityState) - HintCard(compatibilityState) - AboutCard() - } - } - } - } - } - - @OptIn(ExperimentalMaterial3Api::class) - @Composable - private fun TopBar() { - TopAppBar(title = { - Row(verticalAlignment = Alignment.CenterVertically) { - Image( - painter = painterResource(id = R.drawable.ic_5g_big), - contentDescription = null, - colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface), - modifier = Modifier.size(40.dp) - ) - Text(text = "Tile") - } - }) - } - - @Composable - private fun Item(title: String, subtitle: String, link: String?) { - Column(modifier = Modifier - .fillMaxWidth() - .clickable { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link))) } - .padding(0.dp, 5.dp, 0.dp, 5.dp) - ) { - Text(text = title, style = MaterialTheme.typography.labelLarge) - Text(text = subtitle, style = MaterialTheme.typography.labelSmall) - } - } - - @Composable - private fun AboutCard() { - CommonCard(title = stringResource(id = R.string.about)) { - Column { - Text( - text = stringResource(id = R.string.source_code), - style = MaterialTheme.typography.bodyMedium - ) - Column(modifier = Modifier.padding(2.5.dp)) { - Item( - title = "FivegTile", - subtitle = "https://github.com/libxzr/FivegTile", - link = "https://github.com/libxzr/FivegTile" - ) - } - Spacer(modifier = Modifier.height(20.dp)) - Text( - text = stringResource(id = R.string.open_source_licenses), - style = MaterialTheme.typography.bodyMedium - ) - Column(modifier = Modifier.padding(2.5.dp)) { - Item( - title = "libsu", - subtitle = "Apache License 2.0\n" + - "https://github.com/topjohnwu/libsu", - link = "https://github.com/topjohnwu/libsu" - ) - Item( - title = "Android Jetpack", - subtitle = "Apache License 2.0\n" + - "https://android.googlesource.com/platform/frameworks/support", - link = "https://android.googlesource.com/platform/frameworks/support" - ) - Item( - title = "Material Icons", - subtitle = "Apache License 2.0\n" + - "https://material.io/tools/icons", - link = "https://material.io/tools/icons" - ) - Item( - title = "kotlin", - subtitle = "Apache License 2.0\n" + - "https://github.com/JetBrains/kotlin", - link = "https://github.com/JetBrains/kotlin" - ) - } - } - } - } - - @Composable - private fun HintCard(compatibilityState: MainViewModel.CompatibilityState) { - AnimatedVisibility(visible = compatibilityState == MainViewModel.CompatibilityState.COMPATIBLE) { - CommonCard(title = stringResource(id = R.string.hint)) { - Text(text = stringResource(id = R.string.hint_good)) - } - } - AnimatedVisibility(visible = compatibilityState == MainViewModel.CompatibilityState.ROOT_DENIED) { - ErrorCard(title = stringResource(id = R.string.hint)) { - Text(text = stringResource(id = R.string.hint_no_root)) - } - } - AnimatedVisibility(visible = compatibilityState == MainViewModel.CompatibilityState.NOT_COMPATIBLE) { - ErrorCard(title = stringResource(id = R.string.hint)) { - Text(text = stringResource(id = R.string.hint_not_compatible)) - } - } - } - - @Composable - private fun ErrorCard( - title: String, - content: @Composable () -> Unit - ) { - CommonCard( - title = title, - cardColors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.errorContainer, - contentColor = MaterialTheme.colorScheme.onErrorContainer - ), - iconTint = MaterialTheme.colorScheme.error, - content = content - ) - } - - @Composable - private fun CommonCard( - title: String, - cardColors: CardColors = CardDefaults.cardColors(), - iconTint: Color = MaterialTheme.colorScheme.primary, - content: @Composable () -> Unit = {} - ) { - Card( - modifier = Modifier - .fillMaxWidth() - .padding(20.dp, 10.dp, 20.dp, 10.dp), - colors = cardColors - ) { - Column(modifier = Modifier.padding(15.dp)) { - Row( - modifier = Modifier.padding(0.dp, 0.dp, 0.dp, 10.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Image( - painter = painterResource(id = R.drawable.ic_baseline_push_pin_24), - contentDescription = null, - colorFilter = ColorFilter.tint(iconTint), - modifier = Modifier.size(30.dp, 30.dp) - ) - Spacer(modifier = Modifier.width(10.dp)) - Text( - text = title, - style = MaterialTheme.typography.titleMedium - ) - } - Box(modifier = Modifier.padding(5.dp)) { - content() - } - } - } - } - - @Composable - private fun CompatibilityHint(compatibilityState: MainViewModel.CompatibilityState) { - Crossfade(targetState = compatibilityState) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .padding(20.dp), - ) { - Box( - modifier = Modifier.size(100.dp, 100.dp), - contentAlignment = Alignment.Center - ) { - if (it == MainViewModel.CompatibilityState.PENDING) { - CircularProgressIndicator( - modifier = Modifier.size(50.dp, 50.dp), - color = MaterialTheme.colorScheme.primary - ) - } else { - Image( - painter = painterResource( - id = if (it == MainViewModel.CompatibilityState.COMPATIBLE) - R.drawable.ic_baseline_check_24 - else - R.drawable.ic_baseline_error_24 - ), - contentDescription = null, - modifier = Modifier.size(70.dp, 70.dp), - colorFilter = ColorFilter.tint( - when (it) { - MainViewModel.CompatibilityState.NOT_COMPATIBLE, - MainViewModel.CompatibilityState.ROOT_DENIED - -> MaterialTheme.colorScheme.error - else -> MaterialTheme.colorScheme.primary - } - ) - ) - } - } - Text( - text = when (it) { - MainViewModel.CompatibilityState.PENDING -> stringResource(id = R.string.compatibility_pending) - MainViewModel.CompatibilityState.ROOT_DENIED -> stringResource(id = R.string.compatibility_root_not_granted) - MainViewModel.CompatibilityState.NOT_COMPATIBLE -> stringResource(id = R.string.compatibility_not_compatible) - MainViewModel.CompatibilityState.COMPATIBLE -> stringResource(id = R.string.compatibility_good) - }, - style = MaterialTheme.typography.headlineLarge, - color = MaterialTheme.colorScheme.onSurface - ) - } - } - } - - @Preview(showBackground = true) - @Composable - private fun DefaultPreview() { - MainComposable(MainViewModel.CompatibilityState.NOT_COMPATIBLE) - } -} \ No newline at end of file diff --git a/app/src/main/java/moe/xzr/fivegtile/MainViewModel.kt b/app/src/main/java/moe/xzr/fivegtile/MainViewModel.kt deleted file mode 100644 index 2a7ef81..0000000 --- a/app/src/main/java/moe/xzr/fivegtile/MainViewModel.kt +++ /dev/null @@ -1,54 +0,0 @@ -package moe.xzr.fivegtile - -import android.app.Application -import android.content.ComponentName -import android.content.Intent -import android.content.ServiceConnection -import android.os.IBinder -import android.telephony.SubscriptionManager -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.produceState -import androidx.lifecycle.AndroidViewModel -import com.topjohnwu.superuser.ipc.RootService -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -class MainViewModel(application: Application) : AndroidViewModel(application) { - enum class CompatibilityState { - PENDING, - ROOT_DENIED, - NOT_COMPATIBLE, - COMPATIBLE, - } - - @Composable - fun getCompatibilityState(): CompatibilityState { - val ret by produceState(initialValue = CompatibilityState.PENDING) { - if (!withContext(Dispatchers.IO) { - Utils.isRootGranted() - }) { - value = CompatibilityState.ROOT_DENIED - } else { - RootService.bind( - Intent(getApplication(), FivegControllerService::class.java), - object : ServiceConnection { - override fun onServiceConnected(name: ComponentName?, service: IBinder?) { - val fivegController = IFivegController.Stub.asInterface(service) - value = - if (fivegController.compatibilityCheck(SubscriptionManager.getDefaultDataSubscriptionId())) - CompatibilityState.COMPATIBLE - else - CompatibilityState.NOT_COMPATIBLE - } - - override fun onServiceDisconnected(name: ComponentName?) { - - } - - }) - } - } - return ret - } -} \ No newline at end of file diff --git a/app/src/main/java/moe/xzr/fivegtile/Utils.kt b/app/src/main/java/moe/xzr/fivegtile/Utils.kt deleted file mode 100644 index de73479..0000000 --- a/app/src/main/java/moe/xzr/fivegtile/Utils.kt +++ /dev/null @@ -1,10 +0,0 @@ -package moe.xzr.fivegtile - -import com.topjohnwu.superuser.Shell - -object Utils { - fun isRootGranted(): Boolean { - Shell.getShell() - return Shell.isAppGrantedRoot() == true - } -} \ No newline at end of file diff --git a/app/src/main/java/moe/xzr/fivegtile/repository/ShizukuServiceRepository.kt b/app/src/main/java/moe/xzr/fivegtile/repository/ShizukuServiceRepository.kt new file mode 100644 index 0000000..71c0926 --- /dev/null +++ b/app/src/main/java/moe/xzr/fivegtile/repository/ShizukuServiceRepository.kt @@ -0,0 +1,92 @@ +package moe.xzr.fivegtile.repository + +import android.content.ComponentName +import android.content.Context +import android.content.ServiceConnection +import android.content.pm.PackageManager +import android.os.IBinder +import moe.xzr.fivegtile.BuildConfig +import moe.xzr.fivegtile.IFivegController +import moe.xzr.fivegtile.R +import moe.xzr.fivegtile.service.ShizukuControllerService +import rikka.shizuku.Shizuku +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +object ShizukuServiceRepository { + + var userService: IFivegController? = null + private set + + private const val SHIZUKU_PERMISSION_REQUEST_ID = 8 + + suspend fun isSupported(context: Context) = suspendCoroutine { + when { + !Shizuku.pingBinder() -> it.resume(false) + Shizuku.isPreV11() -> it.resume(false) + + Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED -> it.resumeWithServiceBinding(context) + Shizuku.shouldShowRequestPermissionRationale() -> it.resume(false) + + else -> { + val listener = object : Shizuku.OnRequestPermissionResultListener { + override fun onRequestPermissionResult(requestCode: Int, grantResult: Int) { + if (requestCode != SHIZUKU_PERMISSION_REQUEST_ID) return + + when (grantResult == PackageManager.PERMISSION_GRANTED) { + true -> it.resumeWithServiceBinding(context) + + else -> it.resume(false) + } + + Shizuku.removeRequestPermissionResultListener(this) + } + } + + Shizuku.addRequestPermissionResultListener(listener) + Shizuku.requestPermission(SHIZUKU_PERMISSION_REQUEST_ID) + } + } + } + + private fun Continuation.resumeWithServiceBinding(context: Context) { + if (userService != null) { + resume(true) + return + } + + var resumed = false + object : ServiceConnection { + override fun onServiceConnected(componentName: ComponentName?, binder: IBinder?) { + if (binder == null || !binder.pingBinder()) { + if (resumed.not()) { + resume(false) + resumed = true + } + return + } + + userService = IFivegController.Stub.asInterface(binder) + + if (resumed.not()) { + resume(true) + resumed = true + } + } + + override fun onServiceDisconnected(componentName: ComponentName?) { + userService = null + } + }.also { + Shizuku.bindUserService(buildServiceArgs(context), it) + } + } + + private fun buildServiceArgs(context: Context) = + Shizuku.UserServiceArgs(ComponentName(context, ShizukuControllerService::class.java)) + .processNameSuffix("service") + .debuggable(BuildConfig.DEBUG) + .version(BuildConfig.VERSION_CODE) + .tag(context.getString(R.string.app_name)) +} diff --git a/app/src/main/java/moe/xzr/fivegtile/FivegTileService.kt b/app/src/main/java/moe/xzr/fivegtile/service/FivegTileService.kt similarity index 59% rename from app/src/main/java/moe/xzr/fivegtile/FivegTileService.kt rename to app/src/main/java/moe/xzr/fivegtile/service/FivegTileService.kt index 5bae3ba..b6d73f7 100644 --- a/app/src/main/java/moe/xzr/fivegtile/FivegTileService.kt +++ b/app/src/main/java/moe/xzr/fivegtile/service/FivegTileService.kt @@ -1,37 +1,19 @@ -package moe.xzr.fivegtile +package moe.xzr.fivegtile.service -import android.content.ComponentName -import android.content.Intent -import android.content.ServiceConnection -import android.os.IBinder import android.service.quicksettings.Tile import android.service.quicksettings.TileService import android.telephony.SubscriptionManager -import com.topjohnwu.superuser.ipc.RootService +import kotlinx.coroutines.runBlocking +import moe.xzr.fivegtile.IFivegController +import moe.xzr.fivegtile.repository.ShizukuServiceRepository class FivegTileService : TileService() { - private var fivegController: IFivegController? = null - private fun runWithFivegController(what: (IFivegController?) -> Unit) { - if (!Utils.isRootGranted()) { - what(null) - return - } - if (fivegController != null) { - what(fivegController) + private fun runWithFivegController(what: (IFivegController?) -> Unit) = runBlocking { + if (ShizukuServiceRepository.isSupported(this@FivegTileService)) { + what(ShizukuServiceRepository.userService) } else { - RootService.bind( - Intent(this@FivegTileService, FivegControllerService::class.java), - object : ServiceConnection { - override fun onServiceConnected(name: ComponentName?, service: IBinder?) { - fivegController = IFivegController.Stub.asInterface(service) - what(fivegController) - } - - override fun onServiceDisconnected(name: ComponentName?) { - - } - }) + what(null) } } @@ -71,4 +53,4 @@ class FivegTileService : TileService() { refreshState() } } -} \ No newline at end of file +} diff --git a/app/src/main/java/moe/xzr/fivegtile/service/ShizukuControllerService.kt b/app/src/main/java/moe/xzr/fivegtile/service/ShizukuControllerService.kt new file mode 100644 index 0000000..fb64b27 --- /dev/null +++ b/app/src/main/java/moe/xzr/fivegtile/service/ShizukuControllerService.kt @@ -0,0 +1,129 @@ +package moe.xzr.fivegtile.service + +import android.annotation.SuppressLint +import android.content.Context +import android.os.Build +import android.os.ServiceManager +import androidx.annotation.Keep +import com.android.internal.telephony.ITelephony +import moe.xzr.fivegtile.IFivegController + +class ShizukuControllerService() : IFivegController.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) + } + private val typeNr by lazy { + Class.forName("android.telephony.TelephonyManager") + .getDeclaredField("NETWORK_TYPE_BITMASK_NR") + .getLong(null) + } + + @delegate:SuppressLint("PrivateApi", "BlockedPrivateApi") + private val modeLte by lazy { + Class.forName("com.android.internal.telephony.RILConstants") + .getDeclaredField("NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA") + .getInt(null) + } + + @delegate:SuppressLint("PrivateApi", "BlockedPrivateApi") + private val modeNr by lazy { + Class.forName("com.android.internal.telephony.RILConstants") + .getDeclaredField("NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA") + .getInt(null) + } + } + + @Keep + constructor(context: Context) : this() + + override fun compatibilityCheck(subId: Int): Boolean { + return try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + reasonUser + typeNr + iTelephony.setAllowedNetworkTypesForReason( + subId, + reasonUser, + iTelephony.getAllowedNetworkTypesForReason( + subId, + reasonUser + ) + ) + } else { + modeLte + modeNr + // For Q and R. + iTelephony.setPreferredNetworkType( + subId, + iTelephony.getPreferredNetworkType(subId) + ) + } + } catch (_: Exception) { + false + } + } + + override fun getFivegEnabled(subId: Int): Boolean { + return try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + iTelephony.getAllowedNetworkTypesForReason( + subId, + reasonUser + ) and typeNr != 0L + } else { + // For Q and R. + iTelephony.getPreferredNetworkType(subId) == + modeNr + } + } catch (_: Exception) { + false + } + } + + override fun setFivegEnabled(subId: Int, enabled: Boolean) { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + var curTypes = iTelephony.getAllowedNetworkTypesForReason( + subId, + reasonUser + ) + curTypes = if (enabled) { + curTypes or typeNr + } else { + curTypes and typeNr.inv() + } + iTelephony.setAllowedNetworkTypesForReason( + subId, + reasonUser, + curTypes + ) + } else { + // For Q and R. + if (enabled) { + iTelephony.setPreferredNetworkType( + subId, + modeNr + ) + } else { + iTelephony.setPreferredNetworkType( + subId, + modeLte + ) + } + } + } catch (_: Exception) { + + } + } + + override fun destroy() { + + } +} diff --git a/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/MainActivity.kt b/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/MainActivity.kt new file mode 100644 index 0000000..d46e650 --- /dev/null +++ b/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/MainActivity.kt @@ -0,0 +1,47 @@ +package moe.xzr.fivegtile.ui.activity.main + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.viewModels +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.Crossfade +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import moe.xzr.fivegtile.R +import moe.xzr.fivegtile.ui.activity.main.compose.MainScreen +import moe.xzr.fivegtile.ui.theme.FivegTileTheme + +class MainActivity : ComponentActivity() { + + private val viewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + FivegTileTheme { + val state by viewModel.checkCompatibility.collectAsState( + initial = MainViewModel.CompatibilityState.PENDING, + ) + + MainScreen(state = state) + } + } + } +} diff --git a/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/MainViewModel.kt b/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/MainViewModel.kt new file mode 100644 index 0000000..fcd3c98 --- /dev/null +++ b/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/MainViewModel.kt @@ -0,0 +1,36 @@ +package moe.xzr.fivegtile.ui.activity.main + +import android.app.Application +import android.telephony.SubscriptionManager +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState +import androidx.lifecycle.AndroidViewModel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import moe.xzr.fivegtile.repository.ShizukuServiceRepository + +class MainViewModel(application: Application) : AndroidViewModel(application) { + + val checkCompatibility: Flow = flow { + val hasPermission = ShizukuServiceRepository.isSupported(getApplication()) + val service = ShizukuServiceRepository.userService + + if (hasPermission && service != null) { + if (service.compatibilityCheck(SubscriptionManager.getDefaultDataSubscriptionId())) { + CompatibilityState.COMPATIBLE + } else { + CompatibilityState.NOT_COMPATIBLE + } + } else { + CompatibilityState.SHIZUKU_DENIED + }.also { emit(it) } + } + + enum class CompatibilityState { + PENDING, + SHIZUKU_DENIED, + NOT_COMPATIBLE, + COMPATIBLE, + } +} diff --git a/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/compose/MainCards.kt b/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/compose/MainCards.kt new file mode 100644 index 0000000..4d5937e --- /dev/null +++ b/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/compose/MainCards.kt @@ -0,0 +1,227 @@ +package moe.xzr.fivegtile.ui.activity.main.compose + +import android.content.Intent +import android.net.Uri +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.Crossfade +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Card +import androidx.compose.material3.CardColors +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import moe.xzr.fivegtile.R +import moe.xzr.fivegtile.ui.activity.main.MainViewModel + +@Composable +internal fun CompatibilityHint(compatibilityState: MainViewModel.CompatibilityState) { + Crossfade(targetState = compatibilityState, label = "compatibility_hint") { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(20.dp), + ) { + Box( + modifier = Modifier.size(100.dp, 100.dp), + contentAlignment = Alignment.Center + ) { + if (it == MainViewModel.CompatibilityState.PENDING) { + CircularProgressIndicator( + modifier = Modifier.size(50.dp, 50.dp), + color = MaterialTheme.colorScheme.primary + ) + } else { + Image( + painter = painterResource( + id = if (it == MainViewModel.CompatibilityState.COMPATIBLE) + R.drawable.ic_baseline_check_24 + else + R.drawable.ic_baseline_error_24 + ), + contentDescription = null, + modifier = Modifier.size(70.dp, 70.dp), + colorFilter = ColorFilter.tint( + when (it) { + MainViewModel.CompatibilityState.NOT_COMPATIBLE, + MainViewModel.CompatibilityState.SHIZUKU_DENIED + -> MaterialTheme.colorScheme.error + else -> MaterialTheme.colorScheme.primary + } + ) + ) + } + } + Text( + text = when (it) { + MainViewModel.CompatibilityState.PENDING -> stringResource(id = R.string.compatibility_pending) + MainViewModel.CompatibilityState.SHIZUKU_DENIED -> stringResource(id = R.string.compatibility_shizuku_not_granted) + MainViewModel.CompatibilityState.NOT_COMPATIBLE -> stringResource(id = R.string.compatibility_not_compatible) + MainViewModel.CompatibilityState.COMPATIBLE -> stringResource(id = R.string.compatibility_good) + }, + style = MaterialTheme.typography.headlineLarge, + color = MaterialTheme.colorScheme.onSurface + ) + } + } +} + +@Composable +internal fun HintCard(compatibilityState: MainViewModel.CompatibilityState) { + AnimatedVisibility(visible = compatibilityState == MainViewModel.CompatibilityState.COMPATIBLE) { + CommonCard(title = stringResource(id = R.string.hint)) { + Text(text = stringResource(id = R.string.hint_good)) + } + } + AnimatedVisibility(visible = compatibilityState == MainViewModel.CompatibilityState.SHIZUKU_DENIED) { + ErrorCard(title = stringResource(id = R.string.hint)) { + Text(text = stringResource(id = R.string.hint_no_shizuku)) + } + } + AnimatedVisibility(visible = compatibilityState == MainViewModel.CompatibilityState.NOT_COMPATIBLE) { + ErrorCard(title = stringResource(id = R.string.hint)) { + Text(text = stringResource(id = R.string.hint_not_compatible)) + } + } +} + +@Composable +internal fun AboutCard() { + CommonCard(title = stringResource(id = R.string.about)) { + Column { + Text( + text = stringResource(id = R.string.source_code), + style = MaterialTheme.typography.bodyMedium + ) + Column(modifier = Modifier.padding(2.5.dp)) { + Item( + title = "FivegTile", + subtitle = "https://github.com/libxzr/FivegTile", + link = "https://github.com/libxzr/FivegTile" + ) + } + Spacer(modifier = Modifier.height(20.dp)) + Text( + text = stringResource(id = R.string.open_source_licenses), + style = MaterialTheme.typography.bodyMedium + ) + Column(modifier = Modifier.padding(2.5.dp)) { + Item( + title = "Shizuku", + subtitle = "Apache License 2.0\n" + + "https://github.com/RikkaApps/Shizuku", + link = "https://github.com/RikkaApps/Shizuku" + ) + Item( + title = "Android Jetpack", + subtitle = "Apache License 2.0\n" + + "https://android.googlesource.com/platform/frameworks/support", + link = "https://android.googlesource.com/platform/frameworks/support" + ) + Item( + title = "Material Icons", + subtitle = "Apache License 2.0\n" + + "https://material.io/tools/icons", + link = "https://material.io/tools/icons" + ) + Item( + title = "kotlin", + subtitle = "Apache License 2.0\n" + + "https://github.com/JetBrains/kotlin", + link = "https://github.com/JetBrains/kotlin" + ) + } + } + } +} + +@Composable +private fun ErrorCard( + title: String, + content: @Composable () -> Unit +) { + CommonCard( + title = title, + cardColors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.errorContainer, + contentColor = MaterialTheme.colorScheme.onErrorContainer + ), + iconTint = MaterialTheme.colorScheme.error, + content = content + ) +} + +@Composable +private fun CommonCard( + title: String, + cardColors: CardColors = CardDefaults.cardColors(), + iconTint: Color = MaterialTheme.colorScheme.primary, + content: @Composable () -> Unit = {} +) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(20.dp, 10.dp, 20.dp, 10.dp), + colors = cardColors + ) { + Column(modifier = Modifier.padding(15.dp)) { + Row( + modifier = Modifier.padding(0.dp, 0.dp, 0.dp, 10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(id = R.drawable.ic_baseline_push_pin_24), + contentDescription = null, + colorFilter = ColorFilter.tint(iconTint), + modifier = Modifier.size(30.dp, 30.dp) + ) + Spacer(modifier = Modifier.width(10.dp)) + Text( + text = title, + style = MaterialTheme.typography.titleMedium + ) + } + Box(modifier = Modifier.padding(5.dp)) { + content() + } + } + } +} + +@Composable +private fun Item( + title: String, + subtitle: String, + link: String?, +) { + val context = LocalContext.current + + Column(modifier = Modifier + .fillMaxWidth() + .clickable { context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link))) } + .padding(0.dp, 5.dp, 0.dp, 5.dp) + ) { + Text(text = title, style = MaterialTheme.typography.labelLarge) + Text(text = subtitle, style = MaterialTheme.typography.labelSmall) + } +} diff --git a/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/compose/MainScreen.kt b/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/compose/MainScreen.kt new file mode 100644 index 0000000..a5db76f --- /dev/null +++ b/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/compose/MainScreen.kt @@ -0,0 +1,64 @@ +package moe.xzr.fivegtile.ui.activity.main.compose + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import moe.xzr.fivegtile.R +import moe.xzr.fivegtile.ui.activity.main.MainViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MainScreen( + state: MainViewModel.CompatibilityState, +) { + Scaffold( + topBar = { TopBar() }, + ) { paddingValues -> + Box(modifier = Modifier.padding(paddingValues)) { + Column( + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()) + ) { + CompatibilityHint(compatibilityState = state) + HintCard(compatibilityState = state) + AboutCard() + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun TopBar() { + TopAppBar( + title = { + Row(verticalAlignment = Alignment.CenterVertically) { + Image( + painter = painterResource(id = R.drawable.ic_5g_big), + contentDescription = null, + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface), + modifier = Modifier.size(40.dp), + ) + Text(text = "Tile") + } + }, + ) +} diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index ec735e8..56a30f7 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -3,13 +3,11 @@ 提示 现在,您可以将磁贴添加到快速设置中以方便的开关 5G 。 正在检查… - 没有 root 权限 不兼容 兼容性正常 关于 源代码 开放源代码许可 5G - 应用需要 root 权限才能正常工作 :( 很遗憾,应用目前与您所使用的系统不兼容。请考虑在本应用的 Github 仓库中打开一个 issue 并提供足够的信息 :) - \ No newline at end of file + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 946413c..c838433 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3,13 +3,13 @@ Hint Now you can add the tile to the quick settings and toggle 5G conveniently. Checking… - Root not granted + Shizuku permission not granted Not compatible Compatible About Source Code Open Source Licenses 5G - Root permission is needed for this app to work properly :( + Shizuku permission is needed for this app to work properly :( Sorry but this app is not compatible with your system. Consider to open an issue in the Github repository with enough information :) - \ No newline at end of file + diff --git a/build.gradle b/build.gradle index 73a6958..b2a7c57 100644 --- a/build.gradle +++ b/build.gradle @@ -2,10 +2,11 @@ buildscript { ext { compose_version = '1.3.3' libsuVersion = '5.0.4' + shizuku_version = '13.1.5' } -}// Top-level build file where you can add configuration options common to all sub-projects/modules. +} plugins { id 'com.android.application' version '7.4.1' apply false id 'com.android.library' version '7.4.1' apply false id 'org.jetbrains.kotlin.android' version '1.6.10' apply false -} \ No newline at end of file +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 91c8a72..578f06e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Jan 09 13:20:15 CST 2023 +#Sun Jul 14 16:39:47 MSK 2024 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip distributionPath=wrapper/dists -zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists From 7b6d212ca2a46780940f227cce8966fbe9388f75 Mon Sep 17 00:00:00 2001 From: F0x1d Date: Sun, 14 Jul 2024 16:57:10 +0300 Subject: [PATCH 2/2] [refactor]: optimized imports --- .../fivegtile/ui/activity/main/MainActivity.kt | 18 ------------------ .../ui/activity/main/MainViewModel.kt | 3 --- .../ui/activity/main/compose/MainCards.kt | 2 +- 3 files changed, 1 insertion(+), 22 deletions(-) diff --git a/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/MainActivity.kt b/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/MainActivity.kt index d46e650..6e1f466 100644 --- a/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/MainActivity.kt +++ b/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/MainActivity.kt @@ -1,29 +1,11 @@ package moe.xzr.fivegtile.ui.activity.main -import android.content.Intent -import android.net.Uri import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.Crossfade -import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.material3.* -import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import moe.xzr.fivegtile.R import moe.xzr.fivegtile.ui.activity.main.compose.MainScreen import moe.xzr.fivegtile.ui.theme.FivegTileTheme diff --git a/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/MainViewModel.kt b/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/MainViewModel.kt index fcd3c98..b0690bb 100644 --- a/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/MainViewModel.kt +++ b/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/MainViewModel.kt @@ -2,9 +2,6 @@ package moe.xzr.fivegtile.ui.activity.main import android.app.Application import android.telephony.SubscriptionManager -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.produceState import androidx.lifecycle.AndroidViewModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow diff --git a/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/compose/MainCards.kt b/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/compose/MainCards.kt index 4d5937e..f4a5db6 100644 --- a/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/compose/MainCards.kt +++ b/app/src/main/java/moe/xzr/fivegtile/ui/activity/main/compose/MainCards.kt @@ -35,7 +35,7 @@ import moe.xzr.fivegtile.ui.activity.main.MainViewModel @Composable internal fun CompatibilityHint(compatibilityState: MainViewModel.CompatibilityState) { - Crossfade(targetState = compatibilityState, label = "compatibility_hint") { + Crossfade(targetState = compatibilityState) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier