mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-24 18:57:37 +00:00
Handle Activty recreation on content result
Credits to @canyie for the initial PR and finding the bug Close #5791, fix #5789
This commit is contained in:
parent
0469f0b5ae
commit
4eb9240806
@ -36,9 +36,14 @@ abstract class BaseFragment<Binding : ViewDataBinding> : Fragment(), ViewModelHo
|
||||
it.setVariable(BR.viewModel, viewModel)
|
||||
it.lifecycleOwner = viewLifecycleOwner
|
||||
}
|
||||
savedInstanceState?.let { viewModel.onRestoreState(it) }
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
viewModel.onSaveState(outState)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
activity?.supportActionBar?.subtitle = null
|
||||
|
@ -3,6 +3,7 @@ package com.topjohnwu.magisk.arch
|
||||
import android.Manifest.permission.REQUEST_INSTALL_PACKAGES
|
||||
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.databinding.Observable
|
||||
@ -58,6 +59,9 @@ abstract class BaseViewModel(
|
||||
isConnected.addOnPropertyChangedCallback(refreshCallback)
|
||||
}
|
||||
|
||||
open fun onSaveState(state: Bundle) {}
|
||||
open fun onRestoreState(state: Bundle) {}
|
||||
|
||||
/** This should probably never be called manually, it's called manually via delegate. */
|
||||
@Synchronized
|
||||
fun requestRefresh() {
|
||||
|
@ -2,25 +2,37 @@ package com.topjohnwu.magisk.core.base
|
||||
|
||||
import android.Manifest.permission.REQUEST_INSTALL_PACKAGES
|
||||
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultCallback
|
||||
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.R
|
||||
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 com.topjohnwu.magisk.utils.Utils
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
interface ContentResultCallback: ActivityResultCallback<Uri>, Parcelable {
|
||||
fun onActivityLaunch() {}
|
||||
// Make the result type explicitly non-null
|
||||
override fun onActivityResult(result: Uri)
|
||||
}
|
||||
|
||||
abstract class BaseActivity : AppCompatActivity() {
|
||||
|
||||
private var permissionCallback: ((Boolean) -> Unit)? = null
|
||||
@ -33,9 +45,9 @@ abstract class BaseActivity : AppCompatActivity() {
|
||||
permissionCallback = null
|
||||
}
|
||||
|
||||
private var contentCallback: ((Uri) -> Unit)? = null
|
||||
private var contentCallback: ContentResultCallback? = null
|
||||
private val getContent = registerForActivityResult(GetContent()) {
|
||||
if (it != null) contentCallback?.invoke(it)
|
||||
if (it != null) contentCallback?.onActivityResult(it)
|
||||
contentCallback = null
|
||||
}
|
||||
|
||||
@ -62,9 +74,17 @@ abstract class BaseActivity : AppCompatActivity() {
|
||||
clz.reflectField("mActivityHandlesUiModeChecked").set(delegate, true)
|
||||
clz.reflectField("mActivityHandlesUiMode").set(delegate, false)
|
||||
}
|
||||
contentCallback = savedInstanceState?.getParcelable(CONTENT_CALLBACK_KEY)
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
contentCallback?.let {
|
||||
outState.putParcelable(CONTENT_CALLBACK_KEY, it)
|
||||
}
|
||||
}
|
||||
|
||||
fun withPermission(permission: String, callback: (Boolean) -> Unit) {
|
||||
if (permission == WRITE_EXTERNAL_STORAGE && Build.VERSION.SDK_INT >= 30) {
|
||||
// We do not need external rw on 30+
|
||||
@ -79,9 +99,14 @@ abstract class BaseActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
fun getContent(type: String, callback: (Uri) -> Unit) {
|
||||
fun getContent(type: String, callback: ContentResultCallback) {
|
||||
contentCallback = callback
|
||||
try {
|
||||
getContent.launch(type)
|
||||
callback.onActivityLaunch()
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Utils.toast(R.string.app_not_found, Toast.LENGTH_SHORT)
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
@ -100,4 +125,8 @@ abstract class BaseActivity : AppCompatActivity() {
|
||||
startActivity(Intent(intent).setFlags(0))
|
||||
finish()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CONTENT_CALLBACK_KEY = "content_callback"
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,13 @@
|
||||
package com.topjohnwu.magisk.events
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
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.core.base.ContentResultCallback
|
||||
import com.topjohnwu.magisk.utils.TextHolder
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import com.topjohnwu.magisk.utils.asText
|
||||
import com.topjohnwu.magisk.view.Shortcuts
|
||||
|
||||
@ -52,16 +46,12 @@ class RecreateEvent : ViewEvent(), ActivityExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
class MagiskInstallFileEvent(
|
||||
private val callback: (Uri) -> Unit
|
||||
class GetContentEvent(
|
||||
private val type: String,
|
||||
private val callback: ContentResultCallback
|
||||
) : ViewEvent(), ActivityExecutor {
|
||||
override fun invoke(activity: UIActivity<*>) {
|
||||
try {
|
||||
activity.getContent("*/*", callback)
|
||||
Utils.toast(R.string.patch_file_msg, Toast.LENGTH_LONG)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Utils.toast(R.string.app_not_found, Toast.LENGTH_SHORT)
|
||||
}
|
||||
activity.getContent(type, callback)
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,20 +73,6 @@ class AddHomeIconEvent : ViewEvent(), ContextExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
class SelectModuleEvent : ViewEvent(), FragmentExecutor {
|
||||
override fun invoke(fragment: BaseFragment<*>) {
|
||||
try {
|
||||
fragment.apply {
|
||||
activity?.getContent("application/zip") {
|
||||
MainDirections.actionFlashFragment(Const.Value.FLASH_ZIP, it).navigate()
|
||||
}
|
||||
}
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Utils.toast(R.string.app_not_found, Toast.LENGTH_SHORT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SnackbarEvent(
|
||||
private val msg: TextHolder,
|
||||
private val length: Int = Snackbar.LENGTH_SHORT,
|
||||
|
@ -53,7 +53,8 @@ class FlashViewModel : BaseViewModel() {
|
||||
viewModelScope.launch {
|
||||
val result = when (action) {
|
||||
Const.Value.FLASH_ZIP -> {
|
||||
FlashZip(uri!!, outItems, logItems).exec()
|
||||
uri ?: return@launch
|
||||
FlashZip(uri, outItems, logItems).exec()
|
||||
}
|
||||
Const.Value.UNINSTALL -> {
|
||||
showReboot = false
|
||||
|
@ -1,9 +1,5 @@
|
||||
package com.topjohnwu.magisk.ui.install
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseFragment
|
||||
import com.topjohnwu.magisk.databinding.FragmentInstallMd2Binding
|
||||
@ -18,21 +14,4 @@ class InstallFragment : BaseFragment<FragmentInstallMd2Binding>() {
|
||||
super.onStart()
|
||||
requireActivity().setTitle(R.string.install)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
viewModel._method = savedInstanceState?.getInt(KEY_CURRENT_METHOD, -1) ?: -1
|
||||
return super.onCreateView(inflater, container, savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putInt(KEY_CURRENT_METHOD, viewModel.method)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_CURRENT_METHOD = "current_method"
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,34 @@
|
||||
package com.topjohnwu.magisk.ui.install
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import android.widget.Toast
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseViewModel
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.base.ContentResultCallback
|
||||
import com.topjohnwu.magisk.data.repository.NetworkService
|
||||
import com.topjohnwu.magisk.databinding.set
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.events.MagiskInstallFileEvent
|
||||
import com.topjohnwu.magisk.events.GetContentEvent
|
||||
import com.topjohnwu.magisk.events.dialog.SecondSlotWarningDialog
|
||||
import com.topjohnwu.magisk.ui.flash.FlashFragment
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
@ -37,15 +46,15 @@ class InstallViewModel(
|
||||
var step = if (skipOptions) 1 else 0
|
||||
set(value) = set(value, field, { field = it }, BR.step)
|
||||
|
||||
var _method = -1
|
||||
private var methodId = -1
|
||||
|
||||
@get:Bindable
|
||||
var method
|
||||
get() = _method
|
||||
set(value) = set(value, _method, { _method = it }, BR.method) {
|
||||
get() = methodId
|
||||
set(value) = set(value, methodId, { methodId = it }, BR.method) {
|
||||
when (it) {
|
||||
R.id.method_patch -> {
|
||||
MagiskInstallFileEvent { uri -> data = uri }.publish()
|
||||
GetContentEvent("*/*", UriCallback()).publish()
|
||||
}
|
||||
R.id.method_inactive_slot -> {
|
||||
SecondSlotWarningDialog().publish()
|
||||
@ -53,9 +62,7 @@ class InstallViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var data: Uri? = null
|
||||
set(value) = set(value, field, { field = it }, BR.data)
|
||||
val data: LiveData<Uri?> get() = uri
|
||||
|
||||
@get:Bindable
|
||||
var notes: Spanned = SpannableStringBuilder()
|
||||
@ -81,17 +88,60 @@ class InstallViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun step(nextStep: Int) {
|
||||
step = nextStep
|
||||
}
|
||||
|
||||
fun install() {
|
||||
when (method) {
|
||||
R.id.method_patch -> FlashFragment.patch(data!!).navigate(true)
|
||||
R.id.method_patch -> FlashFragment.patch(data.value!!).navigate(true)
|
||||
R.id.method_direct -> FlashFragment.flash(false).navigate(true)
|
||||
R.id.method_inactive_slot -> FlashFragment.flash(true).navigate(true)
|
||||
else -> error("Unknown value")
|
||||
}
|
||||
state = State.LOADING
|
||||
}
|
||||
|
||||
override fun onSaveState(state: Bundle) {
|
||||
state.putParcelable(INSTALL_STATE_KEY, InstallState(
|
||||
methodId,
|
||||
step,
|
||||
Config.keepVerity,
|
||||
Config.keepEnc,
|
||||
Config.patchVbmeta,
|
||||
Config.recovery
|
||||
))
|
||||
}
|
||||
|
||||
override fun onRestoreState(state: Bundle) {
|
||||
state.getParcelable<InstallState>(INSTALL_STATE_KEY)?.let {
|
||||
methodId = it.method
|
||||
step = it.step
|
||||
Config.keepVerity = it.keepVerity
|
||||
Config.keepEnc = it.keepEnc
|
||||
Config.patchVbmeta = it.patchVbmeta
|
||||
Config.recovery = it.recovery
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
class UriCallback : ContentResultCallback {
|
||||
override fun onActivityLaunch() {
|
||||
Utils.toast(R.string.patch_file_msg, Toast.LENGTH_LONG)
|
||||
}
|
||||
override fun onActivityResult(result: Uri) {
|
||||
uri.value = result
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
class InstallState(
|
||||
val method: Int,
|
||||
val step: Int,
|
||||
val keepVerity: Boolean,
|
||||
val keepEnc: Boolean,
|
||||
val patchVbmeta: Boolean,
|
||||
val recovery: Boolean,
|
||||
) : Parcelable
|
||||
|
||||
companion object {
|
||||
private const val INSTALL_STATE_KEY = "install_state"
|
||||
private val uri = MutableLiveData<Uri?>()
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,10 @@ package com.topjohnwu.magisk.ui.module
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.topjohnwu.magisk.MainDirections
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseFragment
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.databinding.FragmentModuleMd2Binding
|
||||
import com.topjohnwu.magisk.databinding.RvItem
|
||||
import com.topjohnwu.magisk.databinding.adapterOf
|
||||
@ -20,8 +22,12 @@ class ModuleFragment : BaseFragment<FragmentModuleMd2Binding>() {
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setHasOptionsMenu(true)
|
||||
activity?.title = resources.getString(R.string.modules)
|
||||
viewModel.data.observe(this) {
|
||||
it ?: return@observe
|
||||
MainDirections.actionFlashFragment(Const.Value.FLASH_ZIP, it).navigate()
|
||||
viewModel.data.value = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
@ -1,22 +1,26 @@
|
||||
package com.topjohnwu.magisk.ui.module
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseViewModel
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.base.ContentResultCallback
|
||||
import com.topjohnwu.magisk.core.model.module.LocalModule
|
||||
import com.topjohnwu.magisk.core.model.module.OnlineModule
|
||||
import com.topjohnwu.magisk.databinding.RvItem
|
||||
import com.topjohnwu.magisk.databinding.diffListOf
|
||||
import com.topjohnwu.magisk.databinding.itemBindingOf
|
||||
import com.topjohnwu.magisk.events.SelectModuleEvent
|
||||
import com.topjohnwu.magisk.events.GetContentEvent
|
||||
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||
import com.topjohnwu.magisk.events.dialog.ModuleInstallDialog
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import me.tatarka.bindingcollectionadapter2.collections.MergeObservableList
|
||||
|
||||
class ModuleViewModel : BaseViewModel() {
|
||||
@ -30,6 +34,8 @@ class ModuleViewModel : BaseViewModel() {
|
||||
it.bindExtra(BR.viewModel, this)
|
||||
}
|
||||
|
||||
val data get() = uri
|
||||
|
||||
init {
|
||||
if (Info.env.isActive) {
|
||||
items.insertItem(InstallModule)
|
||||
@ -70,6 +76,18 @@ class ModuleViewModel : BaseViewModel() {
|
||||
SnackbarEvent(R.string.no_connection).publish()
|
||||
}
|
||||
|
||||
fun installPressed() = withExternalRW { SelectModuleEvent().publish() }
|
||||
|
||||
fun installPressed() = withExternalRW {
|
||||
GetContentEvent("application/zip", UriCallback()).publish()
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
class UriCallback : ContentResultCallback {
|
||||
override fun onActivityResult(result: Uri) {
|
||||
uri.value = result
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val uri = MutableLiveData<Uri?>()
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,7 @@
|
||||
gone="@{viewModel.step != 0}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="@{() -> viewModel.step(1)}"
|
||||
android:onClick="@{() -> viewModel.setStep(1)}"
|
||||
android:text="@string/install_next" />
|
||||
|
||||
</LinearLayout>
|
||||
|
Loading…
x
Reference in New Issue
Block a user