mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-02-26 06:47:24 +00:00
Check REQUEST_INSTALL_PACKAGES before actions
This commit is contained in:
parent
e36596470c
commit
5d400fbe90
@ -1,23 +1,27 @@
|
|||||||
package com.topjohnwu.magisk.arch
|
package com.topjohnwu.magisk.arch
|
||||||
|
|
||||||
import android.content.Context
|
import android.Manifest.permission.REQUEST_INSTALL_PACKAGES
|
||||||
import android.content.Intent
|
import android.annotation.SuppressLint
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.result.contract.ActivityResultContract
|
import android.widget.Toast
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.databinding.ViewDataBinding
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
|
import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
|
||||||
import com.topjohnwu.magisk.R
|
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.core.tasks.HideAPK
|
||||||
import com.topjohnwu.magisk.di.ServiceLocator
|
import com.topjohnwu.magisk.di.ServiceLocator
|
||||||
import com.topjohnwu.magisk.ui.theme.Theme
|
import com.topjohnwu.magisk.ui.theme.Theme
|
||||||
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.magisk.view.MagiskDialog
|
import com.topjohnwu.magisk.view.MagiskDialog
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
import com.topjohnwu.magisk.view.Shortcuts
|
import com.topjohnwu.magisk.view.Shortcuts
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import java.util.concurrent.CountDownLatch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
abstract class BaseMainActivity<Binding : ViewDataBinding> : NavigationActivity<Binding>() {
|
abstract class BaseMainActivity<Binding : ViewDataBinding> : NavigationActivity<Binding>() {
|
||||||
|
|
||||||
@ -25,9 +29,6 @@ abstract class BaseMainActivity<Binding : ViewDataBinding> : NavigationActivity<
|
|||||||
private var doPreload = true
|
private var doPreload = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private val latch = CountDownLatch(1)
|
|
||||||
private val uninstallPkg = registerForActivityResult(UninstallPackage) { latch.countDown() }
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
setTheme(Theme.selected.themeRes)
|
setTheme(Theme.selected.themeRes)
|
||||||
|
|
||||||
@ -67,20 +68,30 @@ abstract class BaseMainActivity<Binding : ViewDataBinding> : NavigationActivity<
|
|||||||
|
|
||||||
abstract fun showMainUI(savedInstanceState: Bundle?)
|
abstract fun showMainUI(savedInstanceState: Bundle?)
|
||||||
|
|
||||||
private fun showInvalidStateMessage() {
|
@SuppressLint("InlinedApi")
|
||||||
runOnUiThread {
|
private fun showInvalidStateMessage(): Unit = runOnUiThread {
|
||||||
MagiskDialog(this).apply {
|
MagiskDialog(this).apply {
|
||||||
setTitle(R.string.unsupport_nonroot_stub_title)
|
setTitle(R.string.unsupport_nonroot_stub_title)
|
||||||
setMessage(R.string.unsupport_nonroot_stub_msg)
|
setMessage(R.string.unsupport_nonroot_stub_msg)
|
||||||
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
||||||
text = R.string.install
|
text = R.string.install
|
||||||
onClick { HideAPK.restore(this@BaseMainActivity) }
|
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)
|
setCancelable(false)
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun preLoad() {
|
private fun preLoad() {
|
||||||
val prevPkg = intent.getStringExtra(Const.Key.PREV_PKG)
|
val prevPkg = intent.getStringExtra(Const.Key.PREV_PKG)
|
||||||
@ -107,23 +118,10 @@ abstract class BaseMainActivity<Binding : ViewDataBinding> : NavigationActivity<
|
|||||||
Config.suManager = ""
|
Config.suManager = ""
|
||||||
pkg ?: return
|
pkg ?: return
|
||||||
if (!Shell.su("(pm uninstall $pkg)& >/dev/null 2>&1").exec().isSuccess) {
|
if (!Shell.su("(pm uninstall $pkg)& >/dev/null 2>&1").exec().isSuccess) {
|
||||||
uninstallPkg.launch(pkg)
|
// Uninstall through Android API
|
||||||
// Wait for the uninstallation to finish
|
uninstallAndWait(pkg)
|
||||||
latch.await()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object UninstallPackage : ActivityResultContract<String, Boolean>() {
|
|
||||||
|
|
||||||
@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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package com.topjohnwu.magisk.arch
|
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.annotation.CallSuper
|
||||||
import androidx.databinding.Bindable
|
import androidx.databinding.Bindable
|
||||||
import androidx.databinding.Observable
|
import androidx.databinding.Observable
|
||||||
@ -78,7 +80,7 @@ abstract class BaseViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline fun withExternalRW(crossinline callback: () -> Unit) {
|
inline fun withExternalRW(crossinline callback: () -> Unit) {
|
||||||
withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) {
|
withPermission(WRITE_EXTERNAL_STORAGE) {
|
||||||
if (!it) {
|
if (!it) {
|
||||||
SnackbarEvent(R.string.external_rw_permission_denied).publish()
|
SnackbarEvent(R.string.external_rw_permission_denied).publish()
|
||||||
} else {
|
} 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 back() = BackPressEvent().publish()
|
||||||
|
|
||||||
fun <Event : ViewEvent> Event.publish() {
|
fun <Event : ViewEvent> Event.publish() {
|
||||||
|
@ -10,6 +10,7 @@ import androidx.core.content.res.use
|
|||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.databinding.DataBindingUtil
|
import androidx.databinding.DataBindingUtil
|
||||||
import androidx.databinding.ViewDataBinding
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.core.Config
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.magisk.core.base.BaseActivity
|
import com.topjohnwu.magisk.core.base.BaseActivity
|
||||||
@ -74,6 +75,13 @@ abstract class UIActivity<Binding : ViewDataBinding> : BaseActivity(), ViewModel
|
|||||||
binding.root.rootView.accessibilityDelegate = delegate
|
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() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
viewModel.requestRefresh()
|
viewModel.requestRefresh()
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.topjohnwu.magisk.core.base
|
package com.topjohnwu.magisk.core.base
|
||||||
|
|
||||||
|
import android.Manifest.permission.REQUEST_INSTALL_PACKAGES
|
||||||
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@ -9,11 +10,16 @@ import android.os.Build
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.GetContent
|
import androidx.activity.result.contract.ActivityResultContracts.GetContent
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
|
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
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.utils.currentLocale
|
||||||
import com.topjohnwu.magisk.core.wrap
|
import com.topjohnwu.magisk.core.wrap
|
||||||
import com.topjohnwu.magisk.ktx.reflectField
|
import com.topjohnwu.magisk.ktx.reflectField
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
abstract class BaseActivity : AppCompatActivity() {
|
abstract class BaseActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@ -22,6 +28,10 @@ abstract class BaseActivity : AppCompatActivity() {
|
|||||||
permissionCallback?.invoke(it)
|
permissionCallback?.invoke(it)
|
||||||
permissionCallback = null
|
permissionCallback = null
|
||||||
}
|
}
|
||||||
|
private val requestInstall = registerForActivityResult(RequestInstall()) {
|
||||||
|
permissionCallback?.invoke(it)
|
||||||
|
permissionCallback = null
|
||||||
|
}
|
||||||
|
|
||||||
private var contentCallback: ((Uri) -> Unit)? = null
|
private var contentCallback: ((Uri) -> Unit)? = null
|
||||||
private val getContent = registerForActivityResult(GetContent()) {
|
private val getContent = registerForActivityResult(GetContent()) {
|
||||||
@ -29,6 +39,11 @@ abstract class BaseActivity : AppCompatActivity() {
|
|||||||
contentCallback = null
|
contentCallback = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var uninstallLatch = CountDownLatch(1)
|
||||||
|
private val uninstallPkg = registerForActivityResult(UninstallPackage()) {
|
||||||
|
uninstallLatch.countDown()
|
||||||
|
}
|
||||||
|
|
||||||
override fun applyOverrideConfiguration(config: Configuration?) {
|
override fun applyOverrideConfiguration(config: Configuration?) {
|
||||||
// Force applying our preferred local
|
// Force applying our preferred local
|
||||||
config?.setLocale(currentLocale)
|
config?.setLocale(currentLocale)
|
||||||
@ -57,14 +72,25 @@ abstract class BaseActivity : AppCompatActivity() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
permissionCallback = callback
|
permissionCallback = callback
|
||||||
|
if (permission == REQUEST_INSTALL_PACKAGES) {
|
||||||
|
requestInstall.launch(Unit)
|
||||||
|
} else {
|
||||||
requestPermission.launch(permission)
|
requestPermission.launch(permission)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getContent(type: String, callback: (Uri) -> Unit) {
|
fun getContent(type: String, callback: (Uri) -> Unit) {
|
||||||
contentCallback = callback
|
contentCallback = callback
|
||||||
getContent.launch(type)
|
getContent.launch(type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun uninstallAndWait(pkg: String) {
|
||||||
|
uninstallLatch = CountDownLatch(1)
|
||||||
|
uninstallPkg.launch(pkg)
|
||||||
|
uninstallLatch.await(3, TimeUnit.SECONDS)
|
||||||
|
}
|
||||||
|
|
||||||
override fun recreate() {
|
override fun recreate() {
|
||||||
startActivity(Intent().setComponent(intent.component))
|
startActivity(Intent().setComponent(intent.component))
|
||||||
finish()
|
finish()
|
||||||
|
@ -21,7 +21,8 @@ import com.topjohnwu.magisk.signing.SignApk
|
|||||||
import com.topjohnwu.magisk.utils.APKInstall
|
import com.topjohnwu.magisk.utils.APKInstall
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
@ -151,9 +152,8 @@ object HideAPK {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@DelicateCoroutinesApi
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
fun restore(activity: Activity) {
|
suspend fun restore(activity: Activity) {
|
||||||
val dialog = ProgressDialog(activity).apply {
|
val dialog = ProgressDialog(activity).apply {
|
||||||
setTitle(activity.getString(R.string.restore_img_msg))
|
setTitle(activity.getString(R.string.restore_img_msg))
|
||||||
isIndeterminate = true
|
isIndeterminate = true
|
||||||
@ -165,12 +165,12 @@ object HideAPK {
|
|||||||
launchApp(activity, APPLICATION_ID)
|
launchApp(activity, APPLICATION_ID)
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
session.install(activity, apk)
|
session.install(activity, apk)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
return@launch
|
return@withContext
|
||||||
}
|
}
|
||||||
session.waitIntent()?.let { activity.startActivity(it) }
|
session.waitIntent()?.let { activity.startActivity(it) }
|
||||||
}
|
}
|
||||||
|
@ -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<Unit, Boolean>() {
|
||||||
|
|
||||||
|
@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<Boolean>? {
|
||||||
|
if (Build.VERSION.SDK_INT < 26)
|
||||||
|
return SynchronousResult(true)
|
||||||
|
if (context.packageManager.canRequestPackageInstalls())
|
||||||
|
return SynchronousResult(true)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
@ -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<String, Boolean>() {
|
||||||
|
|
||||||
|
@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
|
||||||
|
}
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,12 +5,16 @@ import android.content.Context
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.navigation.NavDirections
|
import androidx.navigation.NavDirections
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.topjohnwu.magisk.MainDirections
|
import com.topjohnwu.magisk.MainDirections
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.arch.*
|
import com.topjohnwu.magisk.arch.*
|
||||||
import com.topjohnwu.magisk.core.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.utils.TextHolder
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
|
import com.topjohnwu.magisk.utils.asText
|
||||||
import com.topjohnwu.magisk.view.Shortcuts
|
import com.topjohnwu.magisk.view.Shortcuts
|
||||||
|
|
||||||
class PermissionEvent(
|
class PermissionEvent(
|
||||||
@ -67,7 +71,7 @@ class NavigationEvent(
|
|||||||
) : ViewEvent(), ActivityExecutor {
|
) : ViewEvent(), ActivityExecutor {
|
||||||
override fun invoke(activity: UIActivity<*>) {
|
override fun invoke(activity: UIActivity<*>) {
|
||||||
(activity as? NavigationActivity<*>)?.apply {
|
(activity as? NavigationActivity<*>)?.apply {
|
||||||
if (pop) navigation?.popBackStack()
|
if (pop) navigation.popBackStack()
|
||||||
directions.navigate()
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -123,7 +123,11 @@ class HomeViewModel(
|
|||||||
fun onDeletePressed() = UninstallDialog().publish()
|
fun onDeletePressed() = UninstallDialog().publish()
|
||||||
|
|
||||||
fun onManagerPressed() = when (state) {
|
fun onManagerPressed() = when (state) {
|
||||||
State.LOADED -> withExternalRW { ManagerInstallDialog().publish() }
|
State.LOADED -> withExternalRW {
|
||||||
|
withInstallPermission {
|
||||||
|
ManagerInstallDialog().publish()
|
||||||
|
}
|
||||||
|
}
|
||||||
State.LOADING -> SnackbarEvent(R.string.loading).publish()
|
State.LOADING -> SnackbarEvent(R.string.loading).publish()
|
||||||
else -> SnackbarEvent(R.string.no_connection).publish()
|
else -> SnackbarEvent(R.string.no_connection).publish()
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import com.topjohnwu.magisk.databinding.set
|
|||||||
import com.topjohnwu.magisk.di.AppContext
|
import com.topjohnwu.magisk.di.AppContext
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.magisk.utils.asText
|
import com.topjohnwu.magisk.utils.asText
|
||||||
|
import com.topjohnwu.magisk.view.MagiskDialog
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -105,6 +106,25 @@ object Hide : BaseSettingsItem.Input() {
|
|||||||
object Restore : BaseSettingsItem.Blank() {
|
object Restore : BaseSettingsItem.Blank() {
|
||||||
override val title = R.string.settings_restore_app_title.asText()
|
override val title = R.string.settings_restore_app_title.asText()
|
||||||
override val description = R.string.settings_restore_app_summary.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() {
|
object AddShortcut : BaseSettingsItem.Blank() {
|
||||||
|
@ -20,7 +20,6 @@ import com.topjohnwu.magisk.events.AddHomeIconEvent
|
|||||||
import com.topjohnwu.magisk.events.RecreateEvent
|
import com.topjohnwu.magisk.events.RecreateEvent
|
||||||
import com.topjohnwu.magisk.events.SnackbarEvent
|
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||||
import com.topjohnwu.magisk.events.dialog.BiometricEvent
|
import com.topjohnwu.magisk.events.dialog.BiometricEvent
|
||||||
import com.topjohnwu.magisk.events.dialog.RestoreAppDialog
|
|
||||||
import com.topjohnwu.magisk.ktx.activity
|
import com.topjohnwu.magisk.ktx.activity
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
@ -103,7 +102,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
|
|||||||
Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().navigate()
|
Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().navigate()
|
||||||
DenyListConfig -> SettingsFragmentDirections.actionSettingsFragmentToDenyFragment().navigate()
|
DenyListConfig -> SettingsFragmentDirections.actionSettingsFragmentToDenyFragment().navigate()
|
||||||
SystemlessHosts -> createHosts()
|
SystemlessHosts -> createHosts()
|
||||||
Restore -> RestoreAppDialog().publish()
|
Hide, Restore -> withInstallPermission(andThen)
|
||||||
AddShortcut -> AddHomeIconEvent().publish()
|
AddShortcut -> AddHomeIconEvent().publish()
|
||||||
else -> andThen()
|
else -> andThen()
|
||||||
}
|
}
|
||||||
@ -114,6 +113,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
|
|||||||
Language -> RecreateEvent().publish()
|
Language -> RecreateEvent().publish()
|
||||||
UpdateChannel -> openUrlIfNecessary(view)
|
UpdateChannel -> openUrlIfNecessary(view)
|
||||||
is Hide -> viewModelScope.launch { HideAPK.hide(view.activity, item.value) }
|
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()
|
Zygisk -> if (Zygisk.mismatch) SnackbarEvent(R.string.reboot_apply_change).publish()
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
|
@ -228,6 +228,7 @@
|
|||||||
<string name="unsupport_nonroot_stub_msg">The hidden Magisk app cannot continue to work because root was lost. Please restore the original APK.</string>
|
<string name="unsupport_nonroot_stub_msg">The hidden Magisk app cannot continue to work because root was lost. Please restore the original APK.</string>
|
||||||
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
||||||
<string name="external_rw_permission_denied">Grant storage permission to enable this functionality</string>
|
<string name="external_rw_permission_denied">Grant storage permission to enable this functionality</string>
|
||||||
|
<string name="install_unknown_denied">Allow "install unknown apps" to enable this functionality</string>
|
||||||
<string name="add_shortcut_title">Add shortcut to home screen</string>
|
<string name="add_shortcut_title">Add shortcut to home screen</string>
|
||||||
<string name="add_shortcut_msg">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?</string>
|
<string name="add_shortcut_msg">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?</string>
|
||||||
<string name="app_not_found">No app found to handle this action</string>
|
<string name="app_not_found">No app found to handle this action</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user