mirror of
https://github.com/revanced/revanced-manager-compose.git
synced 2025-04-30 06:14:25 +02:00
fix: don't let user select shizuku installer if permission is not granted
This commit is contained in:
parent
c5d859f990
commit
9c13c66a81
@ -148,6 +148,5 @@ dependencies {
|
|||||||
// Shizuku
|
// Shizuku
|
||||||
implementation("dev.rikka.shizuku:api:13.1.2")
|
implementation("dev.rikka.shizuku:api:13.1.2")
|
||||||
implementation("dev.rikka.shizuku:provider:13.1.2")
|
implementation("dev.rikka.shizuku:provider:13.1.2")
|
||||||
implementation("dev.rikka.tools.refine:runtime:4.3.0")
|
|
||||||
compileOnly("dev.rikka.hidden:stub:4.2.0")
|
compileOnly("dev.rikka.hidden:stub:4.2.0")
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ class PreferencesManager(
|
|||||||
val keystorePass = stringPreference("keystore_pass", KeystoreManager.DEFAULT)
|
val keystorePass = stringPreference("keystore_pass", KeystoreManager.DEFAULT)
|
||||||
|
|
||||||
val preferSplits = booleanPreference("prefer_splits", false)
|
val preferSplits = booleanPreference("prefer_splits", false)
|
||||||
val installer = enumPreference("installer", InstallerManager.DEFAULT)
|
val defaultInstaller = enumPreference("installer", InstallerManager.DEFAULT)
|
||||||
|
|
||||||
val showAutoUpdatesDialog = booleanPreference("show_auto_updates_dialog", true)
|
val showAutoUpdatesDialog = booleanPreference("show_auto_updates_dialog", true)
|
||||||
val managerAutoUpdates = booleanPreference("manager_auto_updates", false)
|
val managerAutoUpdates = booleanPreference("manager_auto_updates", false)
|
||||||
|
@ -1,53 +1,17 @@
|
|||||||
package app.revanced.manager.service
|
package app.revanced.manager.service
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.IPackageManager
|
|
||||||
import android.content.pm.IPackageInstaller
|
|
||||||
import android.content.pm.PackageInstaller
|
|
||||||
import android.content.pm.PackageInstallerHidden
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
|
||||||
import android.os.IBinder
|
|
||||||
import android.os.IInterface
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import app.revanced.manager.rvmApp
|
import app.revanced.manager.rvmApp
|
||||||
import dev.rikka.tools.refine.Refine
|
|
||||||
import rikka.shizuku.Shizuku
|
import rikka.shizuku.Shizuku
|
||||||
import rikka.shizuku.ShizukuBinderWrapper
|
|
||||||
import rikka.shizuku.SystemServiceHelper
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
object ShizukuApi {
|
object ShizukuApi {
|
||||||
|
|
||||||
private fun IBinder.wrap() = ShizukuBinderWrapper(this)
|
|
||||||
private fun IInterface.asShizukuBinder() = this.asBinder().wrap()
|
|
||||||
|
|
||||||
private val iPackageManager: IPackageManager by lazy {
|
|
||||||
IPackageManager.Stub.asInterface(SystemServiceHelper.getSystemService("package").wrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
private val iPackageInstaller: IPackageInstaller by lazy {
|
|
||||||
IPackageInstaller.Stub.asInterface(iPackageManager.packageInstaller.asShizukuBinder())
|
|
||||||
}
|
|
||||||
|
|
||||||
private val packageInstaller: PackageInstaller by lazy {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
Refine.unsafeCast(
|
|
||||||
PackageInstallerHidden(
|
|
||||||
iPackageInstaller,
|
|
||||||
"com.android.shell",
|
|
||||||
null,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Refine.unsafeCast(PackageInstallerHidden(iPackageInstaller, "com.android.shell", 0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var isBinderAvailable = false
|
var isBinderAvailable = false
|
||||||
var isPermissionGranted by mutableStateOf(false)
|
var isPermissionGranted by mutableStateOf(false)
|
||||||
|
|
||||||
@ -71,4 +35,7 @@ object ShizukuApi {
|
|||||||
rvmApp.startActivity(intent)
|
rvmApp.startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isShizukuPermissionGranted() = isBinderAvailable && isPermissionGranted
|
||||||
|
|
||||||
|
fun isShizukuInstalled() = Shizuku.pingBinder()
|
||||||
}
|
}
|
@ -39,7 +39,6 @@ import app.revanced.manager.ui.component.AppTopBar
|
|||||||
import app.revanced.manager.ui.component.bundle.ImportBundleDialog
|
import app.revanced.manager.ui.component.bundle.ImportBundleDialog
|
||||||
import app.revanced.manager.ui.viewmodel.DashboardViewModel
|
import app.revanced.manager.ui.viewmodel.DashboardViewModel
|
||||||
import app.revanced.manager.util.toast
|
import app.revanced.manager.util.toast
|
||||||
import app.revanced.manager.ui.component.ShizukuCard
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.androidx.compose.getViewModel
|
import org.koin.androidx.compose.getViewModel
|
||||||
|
|
||||||
@ -142,7 +141,6 @@ fun DashboardScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ShizukuCard()
|
|
||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
pageCount = pages.size,
|
pageCount = pages.size,
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package app.revanced.manager.ui.screen
|
package app.revanced.manager.ui.screen
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@ -9,9 +10,16 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
|
import app.revanced.manager.service.ShizukuApi
|
||||||
|
import app.revanced.manager.ui.component.ShizukuCard
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun InstalledAppsScreen() {
|
fun InstalledAppsScreen() {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
if (ShizukuApi.isShizukuInstalled()) ShizukuCard()
|
||||||
|
|
||||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.no_patched_apps_found),
|
text = stringResource(R.string.no_patched_apps_found),
|
||||||
@ -19,3 +27,4 @@ fun InstalledAppsScreen() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
package app.revanced.manager.ui.screen.settings
|
package app.revanced.manager.ui.screen.settings
|
||||||
|
|
||||||
import android.app.ActivityManager
|
import android.app.ActivityManager
|
||||||
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@ -41,6 +43,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
import app.revanced.manager.domain.manager.PreferencesManager
|
import app.revanced.manager.domain.manager.PreferencesManager
|
||||||
|
import app.revanced.manager.service.ShizukuApi
|
||||||
import app.revanced.manager.ui.component.AppTopBar
|
import app.revanced.manager.ui.component.AppTopBar
|
||||||
import app.revanced.manager.ui.component.GroupHeader
|
import app.revanced.manager.ui.component.GroupHeader
|
||||||
import app.revanced.manager.ui.viewmodel.AdvancedSettingsViewModel
|
import app.revanced.manager.ui.viewmodel.AdvancedSettingsViewModel
|
||||||
@ -50,8 +53,7 @@ import org.koin.compose.koinInject
|
|||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AdvancedSettingsScreen(
|
fun AdvancedSettingsScreen(
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit, vm: AdvancedSettingsViewModel = getViewModel()
|
||||||
vm: AdvancedSettingsViewModel = getViewModel()
|
|
||||||
) {
|
) {
|
||||||
val prefs = vm.prefs
|
val prefs = vm.prefs
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@ -66,20 +68,15 @@ fun AdvancedSettingsScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (showInstallerPicker) {
|
if (showInstallerPicker) {
|
||||||
InstallerPicker(
|
InstallerPicker(onDismiss = { showInstallerPicker = false },
|
||||||
onDismiss = { showInstallerPicker = false },
|
onConfirm = { vm.setInstaller(it) })
|
||||||
onConfirm = { vm.setInstaller(it) }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(topBar = {
|
||||||
topBar = {
|
|
||||||
AppTopBar(
|
AppTopBar(
|
||||||
title = stringResource(R.string.advanced),
|
title = stringResource(R.string.advanced), onBackClick = onBackClick
|
||||||
onBackClick = onBackClick
|
|
||||||
)
|
)
|
||||||
}
|
}) { paddingValues ->
|
||||||
) { paddingValues ->
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@ -95,65 +92,46 @@ fun AdvancedSettingsScreen(
|
|||||||
it?.let(vm::setApiUrl)
|
it?.let(vm::setApiUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ListItem(
|
ListItem(headlineContent = { Text(stringResource(R.string.api_url)) },
|
||||||
headlineContent = { Text(stringResource(R.string.api_url)) },
|
|
||||||
supportingContent = { Text(apiUrl) },
|
supportingContent = { Text(apiUrl) },
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
showApiUrlDialog = true
|
showApiUrlDialog = true
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
GroupHeader(stringResource(R.string.patch_bundles_section))
|
GroupHeader(stringResource(R.string.patch_bundles_section))
|
||||||
ListItem(
|
ListItem(headlineContent = { Text(stringResource(R.string.patch_bundles_redownload)) },
|
||||||
headlineContent = { Text(stringResource(R.string.patch_bundles_redownload)) },
|
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
vm.redownloadBundles()
|
vm.redownloadBundles()
|
||||||
}
|
})
|
||||||
)
|
ListItem(headlineContent = { Text(stringResource(R.string.patch_bundles_reset)) },
|
||||||
ListItem(
|
|
||||||
headlineContent = { Text(stringResource(R.string.patch_bundles_reset)) },
|
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
vm.resetBundles()
|
vm.resetBundles()
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
val installer by prefs.installer.getAsState()
|
val installer by prefs.defaultInstaller.getAsState()
|
||||||
GroupHeader(stringResource(R.string.installer))
|
GroupHeader(stringResource(R.string.installer))
|
||||||
ListItem(
|
ListItem(modifier = Modifier.clickable { showInstallerPicker = true },
|
||||||
modifier = Modifier.clickable { showInstallerPicker = true },
|
|
||||||
headlineContent = { Text(stringResource(R.string.installer)) },
|
headlineContent = { Text(stringResource(R.string.installer)) },
|
||||||
supportingContent = { Text(stringResource(R.string.installer_description)) },
|
supportingContent = { Text(stringResource(R.string.installer_description)) },
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
FilledTonalButton(
|
FilledTonalButton(colors = ButtonDefaults.filledTonalButtonColors(
|
||||||
colors = ButtonDefaults.filledTonalButtonColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
),
|
), onClick = {
|
||||||
onClick = {
|
|
||||||
showInstallerPicker = true
|
showInstallerPicker = true
|
||||||
}
|
}) {
|
||||||
) {
|
|
||||||
Text(stringResource(installer.displayName))
|
Text(stringResource(installer.displayName))
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
GroupHeader(stringResource(R.string.device))
|
GroupHeader(stringResource(R.string.device))
|
||||||
ListItem(
|
ListItem(headlineContent = { Text(stringResource(R.string.device_model)) },
|
||||||
headlineContent = { Text(stringResource(R.string.device_model)) },
|
supportingContent = { Text(Build.MODEL) })
|
||||||
supportingContent = { Text(Build.MODEL) }
|
ListItem(headlineContent = { Text(stringResource(R.string.device_android_version)) },
|
||||||
)
|
supportingContent = { Text(Build.VERSION.RELEASE) })
|
||||||
ListItem(
|
ListItem(headlineContent = { Text(stringResource(R.string.device_architectures)) },
|
||||||
headlineContent = { Text(stringResource(R.string.device_android_version)) },
|
supportingContent = { Text(Build.SUPPORTED_ABIS.joinToString(", ")) })
|
||||||
supportingContent = { Text(Build.VERSION.RELEASE) }
|
ListItem(headlineContent = { Text(stringResource(R.string.device_memory_limit)) },
|
||||||
)
|
supportingContent = { Text(memoryLimit) })
|
||||||
ListItem(
|
|
||||||
headlineContent = { Text(stringResource(R.string.device_architectures)) },
|
|
||||||
supportingContent = { Text(Build.SUPPORTED_ABIS.joinToString(", ")) }
|
|
||||||
)
|
|
||||||
ListItem(
|
|
||||||
headlineContent = { Text(stringResource(R.string.device_memory_limit)) },
|
|
||||||
supportingContent = { Text(memoryLimit) }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,33 +140,25 @@ fun AdvancedSettingsScreen(
|
|||||||
private fun APIUrlDialog(currentUrl: String, onSubmit: (String?) -> Unit) {
|
private fun APIUrlDialog(currentUrl: String, onSubmit: (String?) -> Unit) {
|
||||||
var url by rememberSaveable(currentUrl) { mutableStateOf(currentUrl) }
|
var url by rememberSaveable(currentUrl) { mutableStateOf(currentUrl) }
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(onDismissRequest = { onSubmit(null) }, confirmButton = {
|
||||||
onDismissRequest = { onSubmit(null) },
|
TextButton(onClick = {
|
||||||
confirmButton = {
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
onSubmit(url)
|
onSubmit(url)
|
||||||
}
|
}) {
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.api_url_dialog_save))
|
Text(stringResource(R.string.api_url_dialog_save))
|
||||||
}
|
}
|
||||||
},
|
}, dismissButton = {
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = { onSubmit(null) }) {
|
TextButton(onClick = { onSubmit(null) }) {
|
||||||
Text(stringResource(R.string.cancel))
|
Text(stringResource(R.string.cancel))
|
||||||
}
|
}
|
||||||
},
|
}, icon = {
|
||||||
icon = {
|
|
||||||
Icon(Icons.Outlined.Http, null)
|
Icon(Icons.Outlined.Http, null)
|
||||||
},
|
}, title = {
|
||||||
title = {
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.api_url_dialog_title),
|
text = stringResource(R.string.api_url_dialog_title),
|
||||||
style = MaterialTheme.typography.headlineSmall.copy(textAlign = TextAlign.Center),
|
style = MaterialTheme.typography.headlineSmall.copy(textAlign = TextAlign.Center),
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
)
|
)
|
||||||
},
|
}, text = {
|
||||||
text = {
|
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
@ -202,14 +172,11 @@ private fun APIUrlDialog(currentUrl: String, onSubmit: (String?) -> Unit) {
|
|||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.error
|
color = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
OutlinedTextField(value = url,
|
||||||
value = url,
|
|
||||||
onValueChange = { url = it },
|
onValueChange = { url = it },
|
||||||
label = { Text(stringResource(R.string.api_url)) }
|
label = { Text(stringResource(R.string.api_url)) })
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -218,10 +185,10 @@ private fun InstallerPicker(
|
|||||||
onConfirm: (PreferencesManager.InstallerManager) -> Unit,
|
onConfirm: (PreferencesManager.InstallerManager) -> Unit,
|
||||||
prefs: PreferencesManager = koinInject()
|
prefs: PreferencesManager = koinInject()
|
||||||
) {
|
) {
|
||||||
var selectedInstaller by rememberSaveable { mutableStateOf(prefs.installer.getBlocking()) }
|
var selectedInstaller by rememberSaveable { mutableStateOf(prefs.defaultInstaller.getBlocking()) }
|
||||||
|
val context: Context = LocalContext.current
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(onDismissRequest = onDismiss,
|
||||||
onDismissRequest = onDismiss,
|
|
||||||
title = { Text(stringResource(R.string.installer)) },
|
title = { Text(stringResource(R.string.installer)) },
|
||||||
text = {
|
text = {
|
||||||
Column {
|
Column {
|
||||||
@ -232,8 +199,7 @@ private fun InstallerPicker(
|
|||||||
.clickable { selectedInstaller = it },
|
.clickable { selectedInstaller = it },
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
RadioButton(
|
RadioButton(selected = selectedInstaller == it,
|
||||||
selected = selectedInstaller == it,
|
|
||||||
onClick = { selectedInstaller = it })
|
onClick = { selectedInstaller = it })
|
||||||
Text(stringResource(it.displayName))
|
Text(stringResource(it.displayName))
|
||||||
}
|
}
|
||||||
@ -242,11 +208,16 @@ private fun InstallerPicker(
|
|||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
Button(onClick = {
|
Button(onClick = {
|
||||||
|
if (selectedInstaller == PreferencesManager.InstallerManager.SHIZUKU && !ShizukuApi.isShizukuPermissionGranted()) {
|
||||||
|
Toast.makeText(
|
||||||
|
context, R.string.shizuku_unavailable, Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
return@Button
|
||||||
|
}
|
||||||
onConfirm(selectedInstaller)
|
onConfirm(selectedInstaller)
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}) {
|
}) {
|
||||||
Text(stringResource(R.string.apply))
|
Text(stringResource(R.string.apply))
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}
|
}
|
@ -36,6 +36,6 @@ class AdvancedSettingsViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setInstaller(installer: PreferencesManager.InstallerManager) = viewModelScope.launch {
|
fun setInstaller(installer: PreferencesManager.InstallerManager) = viewModelScope.launch {
|
||||||
prefs.installer.update(installer)
|
prefs.defaultInstaller.update(installer)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -64,7 +64,7 @@ class InstallerViewModel(input: Destination.Installer) : ViewModel(), KoinCompon
|
|||||||
var installedPackageName by mutableStateOf<String?>(null)
|
var installedPackageName by mutableStateOf<String?>(null)
|
||||||
private set
|
private set
|
||||||
val appButtonText by derivedStateOf { if (installedPackageName == null) R.string.install_app else R.string.open_app }
|
val appButtonText by derivedStateOf { if (installedPackageName == null) R.string.install_app else R.string.open_app }
|
||||||
private val selectedInstaller by derivedStateOf { prefs.installer.getBlocking() }
|
private val selectedInstaller by derivedStateOf { prefs.defaultInstaller.getBlocking() }
|
||||||
|
|
||||||
private val workManager = WorkManager.getInstance(app)
|
private val workManager = WorkManager.getInstance(app)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user