From 5d400fbe9011c16c0e3e555a2e7ee487a8d4795c Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Mon, 14 Feb 2022 02:15:50 -0800 Subject: [PATCH] Check REQUEST_INSTALL_PACKAGES before actions --- .../topjohnwu/magisk/arch/BaseMainActivity.kt | 66 +++++++++---------- .../topjohnwu/magisk/arch/BaseViewModel.kt | 17 ++++- .../com/topjohnwu/magisk/arch/UIActivity.kt | 8 +++ .../magisk/core/base/BaseActivity.kt | 28 +++++++- .../topjohnwu/magisk/core/tasks/HideAPK.kt | 10 +-- .../magisk/core/utils/RequestInstall.kt | 34 ++++++++++ .../magisk/core/utils/UninstallPackage.kt | 21 ++++++ .../topjohnwu/magisk/events/SnackbarEvent.kt | 44 ------------- .../com/topjohnwu/magisk/events/ViewEvents.kt | 29 +++++++- .../magisk/events/dialog/RestoreAppDialog.kt | 22 ------- .../topjohnwu/magisk/ui/home/HomeViewModel.kt | 6 +- .../magisk/ui/settings/SettingsItems.kt | 20 ++++++ .../magisk/ui/settings/SettingsViewModel.kt | 4 +- app/src/main/res/values/strings.xml | 1 + 14 files changed, 198 insertions(+), 112 deletions(-) create mode 100644 app/src/main/java/com/topjohnwu/magisk/core/utils/RequestInstall.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/core/utils/UninstallPackage.kt delete mode 100644 app/src/main/java/com/topjohnwu/magisk/events/SnackbarEvent.kt delete mode 100644 app/src/main/java/com/topjohnwu/magisk/events/dialog/RestoreAppDialog.kt diff --git a/app/src/main/java/com/topjohnwu/magisk/arch/BaseMainActivity.kt b/app/src/main/java/com/topjohnwu/magisk/arch/BaseMainActivity.kt index d44576101..f38f615c9 100644 --- a/app/src/main/java/com/topjohnwu/magisk/arch/BaseMainActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/arch/BaseMainActivity.kt @@ -1,23 +1,27 @@ package com.topjohnwu.magisk.arch -import android.content.Context -import android.content.Intent -import android.net.Uri +import android.Manifest.permission.REQUEST_INSTALL_PACKAGES +import android.annotation.SuppressLint import android.os.Bundle -import androidx.activity.result.contract.ActivityResultContract +import android.widget.Toast import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.databinding.ViewDataBinding +import androidx.lifecycle.lifecycleScope import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID import com.topjohnwu.magisk.R -import com.topjohnwu.magisk.core.* +import com.topjohnwu.magisk.core.Config +import com.topjohnwu.magisk.core.Const +import com.topjohnwu.magisk.core.JobService +import com.topjohnwu.magisk.core.isRunningAsStub import com.topjohnwu.magisk.core.tasks.HideAPK import com.topjohnwu.magisk.di.ServiceLocator import com.topjohnwu.magisk.ui.theme.Theme +import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Shortcuts import com.topjohnwu.superuser.Shell -import java.util.concurrent.CountDownLatch +import kotlinx.coroutines.launch abstract class BaseMainActivity : NavigationActivity() { @@ -25,9 +29,6 @@ abstract class BaseMainActivity : NavigationActivity< private var doPreload = true } - private val latch = CountDownLatch(1) - private val uninstallPkg = registerForActivityResult(UninstallPackage) { latch.countDown() } - override fun onCreate(savedInstanceState: Bundle?) { setTheme(Theme.selected.themeRes) @@ -67,18 +68,28 @@ abstract class BaseMainActivity : NavigationActivity< abstract fun showMainUI(savedInstanceState: Bundle?) - private fun showInvalidStateMessage() { - runOnUiThread { - MagiskDialog(this).apply { - setTitle(R.string.unsupport_nonroot_stub_title) - setMessage(R.string.unsupport_nonroot_stub_msg) - setButton(MagiskDialog.ButtonType.POSITIVE) { - text = R.string.install - onClick { HideAPK.restore(this@BaseMainActivity) } + @SuppressLint("InlinedApi") + private fun showInvalidStateMessage(): Unit = runOnUiThread { + MagiskDialog(this).apply { + setTitle(R.string.unsupport_nonroot_stub_title) + setMessage(R.string.unsupport_nonroot_stub_msg) + setButton(MagiskDialog.ButtonType.POSITIVE) { + text = R.string.install + onClick { + withPermission(REQUEST_INSTALL_PACKAGES) { + if (!it) { + Utils.toast(R.string.install_unknown_denied, Toast.LENGTH_SHORT) + showInvalidStateMessage() + } else { + lifecycleScope.launch { + HideAPK.restore(this@BaseMainActivity) + } + } + } } - setCancelable(false) - show() } + setCancelable(false) + show() } } @@ -107,23 +118,10 @@ abstract class BaseMainActivity : NavigationActivity< Config.suManager = "" pkg ?: return if (!Shell.su("(pm uninstall $pkg)& >/dev/null 2>&1").exec().isSuccess) { - uninstallPkg.launch(pkg) - // Wait for the uninstallation to finish - latch.await() + // Uninstall through Android API + uninstallAndWait(pkg) } } } - object UninstallPackage : ActivityResultContract() { - - @Suppress("DEPRECATION") - override fun createIntent(context: Context, input: String): Intent { - val uri = Uri.Builder().scheme("package").opaquePart(input).build() - val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, uri) - intent.putExtra(Intent.EXTRA_RETURN_RESULT, true) - return intent - } - - override fun parseResult(resultCode: Int, intent: Intent?) = resultCode == RESULT_OK - } } diff --git a/app/src/main/java/com/topjohnwu/magisk/arch/BaseViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/arch/BaseViewModel.kt index 88398424d..6e26454ad 100644 --- a/app/src/main/java/com/topjohnwu/magisk/arch/BaseViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/arch/BaseViewModel.kt @@ -1,6 +1,8 @@ package com.topjohnwu.magisk.arch -import android.Manifest +import android.Manifest.permission.REQUEST_INSTALL_PACKAGES +import android.Manifest.permission.WRITE_EXTERNAL_STORAGE +import android.annotation.SuppressLint import androidx.annotation.CallSuper import androidx.databinding.Bindable import androidx.databinding.Observable @@ -78,7 +80,7 @@ abstract class BaseViewModel( } inline fun withExternalRW(crossinline callback: () -> Unit) { - withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) { + withPermission(WRITE_EXTERNAL_STORAGE) { if (!it) { SnackbarEvent(R.string.external_rw_permission_denied).publish() } else { @@ -87,6 +89,17 @@ abstract class BaseViewModel( } } + @SuppressLint("InlinedApi") + inline fun withInstallPermission(crossinline callback: () -> Unit) { + withPermission(REQUEST_INSTALL_PACKAGES) { + if (!it) { + SnackbarEvent(R.string.install_unknown_denied).publish() + } else { + callback() + } + } + } + fun back() = BackPressEvent().publish() fun Event.publish() { diff --git a/app/src/main/java/com/topjohnwu/magisk/arch/UIActivity.kt b/app/src/main/java/com/topjohnwu/magisk/arch/UIActivity.kt index 9a257aa0c..b3a8ef23e 100644 --- a/app/src/main/java/com/topjohnwu/magisk/arch/UIActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/arch/UIActivity.kt @@ -10,6 +10,7 @@ import androidx.core.content.res.use import androidx.core.view.WindowCompat import androidx.databinding.DataBindingUtil import androidx.databinding.ViewDataBinding +import com.google.android.material.snackbar.Snackbar import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.base.BaseActivity @@ -74,6 +75,13 @@ abstract class UIActivity : BaseActivity(), ViewModel binding.root.rootView.accessibilityDelegate = delegate } + fun showSnackbar( + message: CharSequence, + length: Int = Snackbar.LENGTH_SHORT, + builder: Snackbar.() -> Unit = {} + ) = Snackbar.make(snackbarView, message, length) + .setAnchorView(snackbarAnchorView).apply(builder).show() + override fun onResume() { super.onResume() viewModel.requestRefresh() diff --git a/app/src/main/java/com/topjohnwu/magisk/core/base/BaseActivity.kt b/app/src/main/java/com/topjohnwu/magisk/core/base/BaseActivity.kt index ca0ba8222..ae0d0e131 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/base/BaseActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/base/BaseActivity.kt @@ -1,5 +1,6 @@ package com.topjohnwu.magisk.core.base +import android.Manifest.permission.REQUEST_INSTALL_PACKAGES import android.Manifest.permission.WRITE_EXTERNAL_STORAGE import android.content.Context import android.content.Intent @@ -9,11 +10,16 @@ import android.os.Build import android.os.Bundle import androidx.activity.result.contract.ActivityResultContracts.GetContent import androidx.activity.result.contract.ActivityResultContracts.RequestPermission +import androidx.annotation.WorkerThread import androidx.appcompat.app.AppCompatActivity import com.topjohnwu.magisk.core.isRunningAsStub +import com.topjohnwu.magisk.core.utils.RequestInstall +import com.topjohnwu.magisk.core.utils.UninstallPackage import com.topjohnwu.magisk.core.utils.currentLocale import com.topjohnwu.magisk.core.wrap import com.topjohnwu.magisk.ktx.reflectField +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit abstract class BaseActivity : AppCompatActivity() { @@ -22,6 +28,10 @@ abstract class BaseActivity : AppCompatActivity() { permissionCallback?.invoke(it) permissionCallback = null } + private val requestInstall = registerForActivityResult(RequestInstall()) { + permissionCallback?.invoke(it) + permissionCallback = null + } private var contentCallback: ((Uri) -> Unit)? = null private val getContent = registerForActivityResult(GetContent()) { @@ -29,6 +39,11 @@ abstract class BaseActivity : AppCompatActivity() { contentCallback = null } + private var uninstallLatch = CountDownLatch(1) + private val uninstallPkg = registerForActivityResult(UninstallPackage()) { + uninstallLatch.countDown() + } + override fun applyOverrideConfiguration(config: Configuration?) { // Force applying our preferred local config?.setLocale(currentLocale) @@ -57,7 +72,11 @@ abstract class BaseActivity : AppCompatActivity() { return } permissionCallback = callback - requestPermission.launch(permission) + if (permission == REQUEST_INSTALL_PACKAGES) { + requestInstall.launch(Unit) + } else { + requestPermission.launch(permission) + } } fun getContent(type: String, callback: (Uri) -> Unit) { @@ -65,6 +84,13 @@ abstract class BaseActivity : AppCompatActivity() { getContent.launch(type) } + @WorkerThread + fun uninstallAndWait(pkg: String) { + uninstallLatch = CountDownLatch(1) + uninstallPkg.launch(pkg) + uninstallLatch.await(3, TimeUnit.SECONDS) + } + override fun recreate() { startActivity(Intent().setComponent(intent.component)) finish() diff --git a/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt b/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt index 931add738..4ae652927 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt @@ -21,7 +21,8 @@ import com.topjohnwu.magisk.signing.SignApk import com.topjohnwu.magisk.utils.APKInstall import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.superuser.Shell -import kotlinx.coroutines.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import timber.log.Timber import java.io.File import java.io.FileOutputStream @@ -151,9 +152,8 @@ object HideAPK { } } - @DelicateCoroutinesApi @Suppress("DEPRECATION") - fun restore(activity: Activity) { + suspend fun restore(activity: Activity) { val dialog = ProgressDialog(activity).apply { setTitle(activity.getString(R.string.restore_img_msg)) isIndeterminate = true @@ -165,12 +165,12 @@ object HideAPK { launchApp(activity, APPLICATION_ID) dialog.dismiss() } - GlobalScope.launch(Dispatchers.IO) { + withContext(Dispatchers.IO) { try { session.install(activity, apk) } catch (e: IOException) { Timber.e(e) - return@launch + return@withContext } session.waitIntent()?.let { activity.startActivity(it) } } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/utils/RequestInstall.kt b/app/src/main/java/com/topjohnwu/magisk/core/utils/RequestInstall.kt new file mode 100644 index 000000000..54aa1ce05 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/core/utils/RequestInstall.kt @@ -0,0 +1,34 @@ +package com.topjohnwu.magisk.core.utils + +import android.annotation.TargetApi +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.provider.Settings +import androidx.activity.result.contract.ActivityResultContract + +class RequestInstall : ActivityResultContract() { + + @TargetApi(26) + override fun createIntent(context: Context, input: Unit): Intent { + // This will only be called on API 26+ + return Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES) + .setData(Uri.parse("package:${context.packageName}")) + } + + override fun parseResult(resultCode: Int, intent: Intent?) = + resultCode == Activity.RESULT_OK + + override fun getSynchronousResult( + context: Context, + input: Unit + ): SynchronousResult? { + if (Build.VERSION.SDK_INT < 26) + return SynchronousResult(true) + if (context.packageManager.canRequestPackageInstalls()) + return SynchronousResult(true) + return null + } +} diff --git a/app/src/main/java/com/topjohnwu/magisk/core/utils/UninstallPackage.kt b/app/src/main/java/com/topjohnwu/magisk/core/utils/UninstallPackage.kt new file mode 100644 index 000000000..a8446a5c2 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/core/utils/UninstallPackage.kt @@ -0,0 +1,21 @@ +package com.topjohnwu.magisk.core.utils + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.activity.result.contract.ActivityResultContract + +class UninstallPackage : ActivityResultContract() { + + @Suppress("DEPRECATION") + override fun createIntent(context: Context, input: String): Intent { + val uri = Uri.Builder().scheme("package").opaquePart(input).build() + val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, uri) + intent.putExtra(Intent.EXTRA_RETURN_RESULT, true) + return intent + } + + override fun parseResult(resultCode: Int, intent: Intent?) = + resultCode == Activity.RESULT_OK +} diff --git a/app/src/main/java/com/topjohnwu/magisk/events/SnackbarEvent.kt b/app/src/main/java/com/topjohnwu/magisk/events/SnackbarEvent.kt deleted file mode 100644 index 9f67021dc..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/events/SnackbarEvent.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.topjohnwu.magisk.events - -import android.view.View -import androidx.annotation.StringRes -import com.google.android.material.snackbar.Snackbar -import com.topjohnwu.magisk.arch.ActivityExecutor -import com.topjohnwu.magisk.arch.UIActivity -import com.topjohnwu.magisk.arch.ViewEvent -import com.topjohnwu.magisk.utils.TextHolder -import com.topjohnwu.magisk.utils.asText - -class SnackbarEvent constructor( - private val msg: TextHolder, - private val length: Int = Snackbar.LENGTH_SHORT, - private val builder: Snackbar.() -> Unit = {} -) : ViewEvent(), ActivityExecutor { - - constructor( - @StringRes res: Int, - length: Int = Snackbar.LENGTH_SHORT, - builder: Snackbar.() -> Unit = {} - ) : this(res.asText(), length, builder) - - constructor( - msg: String, - length: Int = Snackbar.LENGTH_SHORT, - builder: Snackbar.() -> Unit = {} - ) : this(msg.asText(), length, builder) - - - private fun snackbar( - view: View, - anchor: View?, - message: String, - length: Int, - builder: Snackbar.() -> Unit - ) = Snackbar.make(view, message, length).setAnchorView(anchor).apply(builder).show() - - override fun invoke(activity: UIActivity<*>) { - snackbar(activity.snackbarView, activity.snackbarAnchorView, - msg.getText(activity.resources).toString(), - length, builder) - } -} diff --git a/app/src/main/java/com/topjohnwu/magisk/events/ViewEvents.kt b/app/src/main/java/com/topjohnwu/magisk/events/ViewEvents.kt index 247c644e2..3421e4ebf 100644 --- a/app/src/main/java/com/topjohnwu/magisk/events/ViewEvents.kt +++ b/app/src/main/java/com/topjohnwu/magisk/events/ViewEvents.kt @@ -5,12 +5,16 @@ import android.content.Context import android.net.Uri import android.view.View import android.widget.Toast +import androidx.annotation.StringRes import androidx.navigation.NavDirections +import com.google.android.material.snackbar.Snackbar import com.topjohnwu.magisk.MainDirections import com.topjohnwu.magisk.R import com.topjohnwu.magisk.arch.* import com.topjohnwu.magisk.core.Const +import com.topjohnwu.magisk.utils.TextHolder import com.topjohnwu.magisk.utils.Utils +import com.topjohnwu.magisk.utils.asText import com.topjohnwu.magisk.view.Shortcuts class PermissionEvent( @@ -67,7 +71,7 @@ class NavigationEvent( ) : ViewEvent(), ActivityExecutor { override fun invoke(activity: UIActivity<*>) { (activity as? NavigationActivity<*>)?.apply { - if (pop) navigation?.popBackStack() + if (pop) navigation.popBackStack() directions.navigate() } } @@ -92,3 +96,26 @@ class SelectModuleEvent : ViewEvent(), FragmentExecutor { } } } + +class SnackbarEvent( + private val msg: TextHolder, + private val length: Int = Snackbar.LENGTH_SHORT, + private val builder: Snackbar.() -> Unit = {} +) : ViewEvent(), ActivityExecutor { + + constructor( + @StringRes res: Int, + length: Int = Snackbar.LENGTH_SHORT, + builder: Snackbar.() -> Unit = {} + ) : this(res.asText(), length, builder) + + constructor( + msg: String, + length: Int = Snackbar.LENGTH_SHORT, + builder: Snackbar.() -> Unit = {} + ) : this(msg.asText(), length, builder) + + override fun invoke(activity: UIActivity<*>) { + activity.showSnackbar(msg.getText(activity.resources), length, builder) + } +} diff --git a/app/src/main/java/com/topjohnwu/magisk/events/dialog/RestoreAppDialog.kt b/app/src/main/java/com/topjohnwu/magisk/events/dialog/RestoreAppDialog.kt deleted file mode 100644 index eacc8ea72..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/events/dialog/RestoreAppDialog.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.topjohnwu.magisk.events.dialog - -import com.topjohnwu.magisk.R -import com.topjohnwu.magisk.core.tasks.HideAPK -import com.topjohnwu.magisk.view.MagiskDialog - -class RestoreAppDialog : DialogEvent() { - override fun build(dialog: MagiskDialog) { - dialog.apply { - setTitle(R.string.settings_restore_app_title) - setMessage(R.string.restore_app_confirmation) - setButton(MagiskDialog.ButtonType.POSITIVE) { - text = android.R.string.ok - onClick { HideAPK.restore(dialog.ownerActivity!!) } - } - setButton(MagiskDialog.ButtonType.NEGATIVE) { - text = android.R.string.cancel - } - setCancelable(true) - } - } -} diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt index ca89d3bd9..54772bdc3 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt @@ -123,7 +123,11 @@ class HomeViewModel( fun onDeletePressed() = UninstallDialog().publish() fun onManagerPressed() = when (state) { - State.LOADED -> withExternalRW { ManagerInstallDialog().publish() } + State.LOADED -> withExternalRW { + withInstallPermission { + ManagerInstallDialog().publish() + } + } State.LOADING -> SnackbarEvent(R.string.loading).publish() else -> SnackbarEvent(R.string.no_connection).publish() } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsItems.kt b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsItems.kt index e195950fb..ea6cca17d 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsItems.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsItems.kt @@ -25,6 +25,7 @@ import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.di.AppContext import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.asText +import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.superuser.Shell import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -105,6 +106,25 @@ object Hide : BaseSettingsItem.Input() { object Restore : BaseSettingsItem.Blank() { override val title = R.string.settings_restore_app_title.asText() override val description = R.string.settings_restore_app_summary.asText() + + override fun onPressed(view: View, handler: Handler) { + handler.onItemPressed(view, this) { + MagiskDialog(view.context).apply { + setTitle(R.string.settings_restore_app_title) + setMessage(R.string.restore_app_confirmation) + setButton(MagiskDialog.ButtonType.POSITIVE) { + text = android.R.string.ok + onClick { + handler.onItemAction(view, this@Restore) + } + } + setButton(MagiskDialog.ButtonType.NEGATIVE) { + text = android.R.string.cancel + } + setCancelable(true) + } + } + } } object AddShortcut : BaseSettingsItem.Blank() { diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsViewModel.kt index 6b1ea3813..ca9f78875 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsViewModel.kt @@ -20,7 +20,6 @@ import com.topjohnwu.magisk.events.AddHomeIconEvent import com.topjohnwu.magisk.events.RecreateEvent import com.topjohnwu.magisk.events.SnackbarEvent import com.topjohnwu.magisk.events.dialog.BiometricEvent -import com.topjohnwu.magisk.events.dialog.RestoreAppDialog import com.topjohnwu.magisk.ktx.activity import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.superuser.Shell @@ -103,7 +102,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler { Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().navigate() DenyListConfig -> SettingsFragmentDirections.actionSettingsFragmentToDenyFragment().navigate() SystemlessHosts -> createHosts() - Restore -> RestoreAppDialog().publish() + Hide, Restore -> withInstallPermission(andThen) AddShortcut -> AddHomeIconEvent().publish() else -> andThen() } @@ -114,6 +113,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler { Language -> RecreateEvent().publish() UpdateChannel -> openUrlIfNecessary(view) is Hide -> viewModelScope.launch { HideAPK.hide(view.activity, item.value) } + Restore -> viewModelScope.launch { HideAPK.restore(view.activity) } Zygisk -> if (Zygisk.mismatch) SnackbarEvent(R.string.reboot_apply_change).publish() else -> Unit } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index afe34bd19..fc3ae1ea9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -228,6 +228,7 @@ The hidden Magisk app cannot continue to work because root was lost. Please restore the original APK. @string/settings_restore_app_title Grant storage permission to enable this functionality + Allow "install unknown apps" to enable this functionality Add shortcut to home screen After hiding this app, its name and icon might become difficult to recognize. Do you want to add a pretty shortcut to the home screen? No app found to handle this action