diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b9e07e1..17fddfc 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -65,6 +65,9 @@ dependencies { debugImplementation(libs.ui.tooling) debugImplementation(libs.ui.test.manifest) + implementation(libs.libsu.core) + implementation(libs.libsu.service) + implementation(libs.core.ktx) implementation(libs.lifecycle.runtime.ktx) implementation(libs.activity.compose) diff --git a/app/src/main/java/vegabobo/languageselector/MainActivity.kt b/app/src/main/java/vegabobo/languageselector/MainActivity.kt index da929fb..861e309 100644 --- a/app/src/main/java/vegabobo/languageselector/MainActivity.kt +++ b/app/src/main/java/vegabobo/languageselector/MainActivity.kt @@ -1,17 +1,21 @@ package vegabobo.languageselector import android.content.ComponentName +import android.content.Intent import android.content.pm.PackageManager import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.core.view.WindowCompat +import com.topjohnwu.superuser.Shell +import com.topjohnwu.superuser.ipc.RootService +import dagger.hilt.android.AndroidEntryPoint +import rikka.shizuku.Shizuku +import vegabobo.languageselector.service.RootUserService import vegabobo.languageselector.service.UserService import vegabobo.languageselector.service.UserServiceProvider import vegabobo.languageselector.ui.screen.Navigation import vegabobo.languageselector.ui.theme.LanguageSelector -import dagger.hilt.android.AndroidEntryPoint -import rikka.shizuku.Shizuku object ShizukuArgs { val userServiceArgs = @@ -24,9 +28,15 @@ object ShizukuArgs { .version(BuildConfig.VERSION_CODE) } + @AndroidEntryPoint class MainActivity : ComponentActivity(), Shizuku.OnRequestPermissionResultListener { + init { + Shell.enableVerboseLogging = BuildConfig.DEBUG + Shell.setDefaultBuilder(Shell.Builder.create().setTimeout(10)) + } + val acRequestCode = 1 fun bindShizuku() { @@ -64,6 +74,13 @@ class MainActivity : ComponentActivity(), Shizuku.OnRequestPermissionResultListe Shizuku.addRequestPermissionResultListener(REQUEST_PERMISSION_RESULT_LISTENER) checkPermission(acRequestCode) } + + RootReceivedListener.setListener(object : IRootListener { + override fun onRootReceived() { + val intent = Intent(this@MainActivity, RootUserService::class.java) + RootService.bind(intent, UserServiceProvider.connection) + } + }) } override fun onResume() { @@ -80,7 +97,11 @@ class MainActivity : ComponentActivity(), Shizuku.OnRequestPermissionResultListe override fun onDestroy() { Shizuku.removeRequestPermissionResultListener(REQUEST_PERMISSION_RESULT_LISTENER) if (UserServiceProvider.isConnected()) - Shizuku.unbindUserService(ShizukuArgs.userServiceArgs, UserServiceProvider.connection, true) + Shizuku.unbindUserService( + ShizukuArgs.userServiceArgs, + UserServiceProvider.connection, + true + ) super.onDestroy() } diff --git a/app/src/main/java/vegabobo/languageselector/RootReceivedListener.kt b/app/src/main/java/vegabobo/languageselector/RootReceivedListener.kt new file mode 100644 index 0000000..f4ff50d --- /dev/null +++ b/app/src/main/java/vegabobo/languageselector/RootReceivedListener.kt @@ -0,0 +1,18 @@ +package vegabobo.languageselector + + +interface IRootListener { + fun onRootReceived() +} + +object RootReceivedListener { + var callback: IRootListener? = null + + fun setListener(inCallback: IRootListener?) { + callback = inCallback + } + + fun onRootReceived() { + callback?.onRootReceived() + } +} \ No newline at end of file diff --git a/app/src/main/java/vegabobo/languageselector/service/RootUserService.kt b/app/src/main/java/vegabobo/languageselector/service/RootUserService.kt new file mode 100644 index 0000000..293bb0c --- /dev/null +++ b/app/src/main/java/vegabobo/languageselector/service/RootUserService.kt @@ -0,0 +1,11 @@ +package vegabobo.languageselector.service + +import android.content.Intent +import android.os.IBinder +import com.topjohnwu.superuser.ipc.RootService + +class RootUserService : RootService() { + override fun onBind(intent: Intent): IBinder { + return UserService() + } +} \ No newline at end of file diff --git a/app/src/main/java/vegabobo/languageselector/ui/screen/main/MainScreen.kt b/app/src/main/java/vegabobo/languageselector/ui/screen/main/MainScreen.kt index a54a663..35514e3 100644 --- a/app/src/main/java/vegabobo/languageselector/ui/screen/main/MainScreen.kt +++ b/app/src/main/java/vegabobo/languageselector/ui/screen/main/MainScreen.kt @@ -41,7 +41,7 @@ fun MainScreen( ) } ) { paddingValues -> - if (!uiState.isShizukuAvail) { + if (uiState.operationMode == OperationMode.NONE) { ShizukuRequiredWarning { mainScreenVm.onClickProceedShizuku() } } LazyColumn( diff --git a/app/src/main/java/vegabobo/languageselector/ui/screen/main/MainScreenState.kt b/app/src/main/java/vegabobo/languageselector/ui/screen/main/MainScreenState.kt index af4c710..79218e6 100644 --- a/app/src/main/java/vegabobo/languageselector/ui/screen/main/MainScreenState.kt +++ b/app/src/main/java/vegabobo/languageselector/ui/screen/main/MainScreenState.kt @@ -5,14 +5,18 @@ import android.content.pm.PackageManager import android.graphics.drawable.Drawable import androidx.compose.runtime.mutableStateListOf +enum class OperationMode { + NONE, SHIZUKU, ROOT +} + data class MainScreenState( val searchTextFieldValue: String = "", val isLoading: Boolean = true, val isDropdownVisible: Boolean = false, val isShowingSystemApps: Boolean = false, - val isShizukuAvail: Boolean = false, + val operationMode: OperationMode = OperationMode.NONE, val isSearchVisible: Boolean = false, - val isSystemAppDialogVisible: Boolean = false, + val isSystemAppDialogVisible: Boolean = false, val isAboutDialogVisible: Boolean = false, val listOfApps: MutableList = mutableStateListOf() ) diff --git a/app/src/main/java/vegabobo/languageselector/ui/screen/main/MainScreenVm.kt b/app/src/main/java/vegabobo/languageselector/ui/screen/main/MainScreenVm.kt index c84904f..b52b613 100644 --- a/app/src/main/java/vegabobo/languageselector/ui/screen/main/MainScreenVm.kt +++ b/app/src/main/java/vegabobo/languageselector/ui/screen/main/MainScreenVm.kt @@ -8,6 +8,7 @@ import android.os.Looper import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.topjohnwu.superuser.Shell import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -17,6 +18,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import rikka.shizuku.Shizuku import vegabobo.languageselector.BuildConfig +import vegabobo.languageselector.RootReceivedListener import javax.inject.Inject @@ -28,16 +30,29 @@ class MainScreenVm @Inject constructor( private val _uiState = MutableStateFlow(MainScreenState()) val uiState: StateFlow = _uiState.asStateFlow() - fun refreshShizukuAvail() { + fun loadOperationMode() { + if(Shell.getShell().isAlive) + Shell.getShell().close() + Shell.getShell() + if(Shell.isAppGrantedRoot() == true) { + _uiState.update { it.copy(operationMode = OperationMode.ROOT) } + RootReceivedListener.onRootReceived() + return + } + val isAvail = Shizuku.pingBinder() && Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED - if (isAvail) - _uiState.update { it.copy(isShizukuAvail = true) } + if (isAvail) { + _uiState.update { it.copy(operationMode = OperationMode.SHIZUKU) } + return + } + + _uiState.update { it.copy(operationMode = OperationMode.NONE) } } init { + loadOperationMode() fillListOfApps() - refreshShizukuAvail() } fun fillListOfApps(getAlsoSystemApps: Boolean = false) { @@ -104,7 +119,7 @@ class MainScreenVm @Inject constructor( } fun onClickProceedShizuku() { - refreshShizukuAvail() + loadOperationMode() } fun toggleSearch() { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 54be6dd..e6b5677 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,6 @@ [versions] aboutlibraries = "10.6.3" +libsu = "6.0.0" hiddenapibypass = "4.3" hilt = "2.49" hilt-navigation-compose = "1.2.0" @@ -21,6 +22,8 @@ androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-com androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle-runtime-ktx" } androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "material-icons-extended" } androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigation-compose" } +libsu-core = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" } +libsu-service = { module = "com.github.topjohnwu.libsu:service", version.ref = "libsu" } hiddenapibypass = { module = "org.lsposed.hiddenapibypass:hiddenapibypass", version.ref = "hiddenapibypass" } hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 180d64a..600798b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,6 +3,7 @@ pluginManagement { google() mavenCentral() gradlePluginPortal() + maven("https://jitpack.io") } } dependencyResolutionManagement { @@ -10,6 +11,7 @@ dependencyResolutionManagement { repositories { google() mavenCentral() + maven("https://jitpack.io") } }