Migrate to Activity Result APIs

This commit is contained in:
topjohnwu 2021-11-05 15:53:34 -07:00
parent 98deec232b
commit 1bdd6e1a9d
6 changed files with 50 additions and 161 deletions

View File

@ -1,8 +1,10 @@
package com.topjohnwu.magisk.arch
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContract
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.databinding.ViewDataBinding
import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
@ -23,12 +25,13 @@ import java.util.concurrent.CountDownLatch
abstract class BaseMainActivity<VM : BaseViewModel, Binding : ViewDataBinding>
: BaseUIActivity<VM, Binding>() {
private val latch = CountDownLatch(1)
companion object {
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)
@ -107,19 +110,24 @@ abstract class BaseMainActivity<VM : BaseViewModel, Binding : ViewDataBinding>
if (Config.suManager.isNotEmpty())
Config.suManager = ""
pkg ?: return
if (!Shell.su("(pm uninstall $pkg)& >/dev/null 2>&1").exec().isSuccess)
uninstallApp(pkg)
}
}
@Suppress("DEPRECATION")
private fun uninstallApp(pkg: String) {
val uri = Uri.Builder().scheme("package").opaquePart(pkg).build()
val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, uri)
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true)
startActivityForResult(intent) { _, _ ->
latch.countDown()
}
if (!Shell.su("(pm uninstall $pkg)& >/dev/null 2>&1").exec().isSuccess) {
uninstallPkg.launch(pkg)
// Wait for the uninstallation to finish
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
}
}

View File

@ -77,7 +77,7 @@ abstract class BaseViewModel(
PermissionEvent(permission, callback).publish()
}
fun withExternalRW(callback: () -> Unit) {
inline fun withExternalRW(crossinline callback: () -> Unit) {
withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) {
if (!it) {
SnackbarEvent(R.string.external_rw_permission_denied).publish()

View File

@ -1,39 +1,32 @@
package com.topjohnwu.magisk.core.base
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.annotation.CallSuper
import androidx.activity.result.contract.ActivityResultContracts.GetContent
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
import androidx.appcompat.app.AppCompatActivity
import androidx.collection.SparseArrayCompat
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.core.wrap
import com.topjohnwu.magisk.ktx.reflectField
import com.topjohnwu.magisk.ktx.set
import com.topjohnwu.magisk.utils.Utils
import kotlin.random.Random
typealias ActivityResultCallback = BaseActivity.(Int, Intent?) -> Unit
abstract class BaseActivity : AppCompatActivity() {
private val resultCallbacks by lazy { SparseArrayCompat<ActivityResultCallback>() }
private val newRequestCode: Int get() {
var requestCode: Int
do {
requestCode = Random.nextInt(0, 1 shl 15)
} while (resultCallbacks.containsKey(requestCode))
return requestCode
private var permissionCallback: ((Boolean) -> Unit)? = null
private val requestPermission = registerForActivityResult(RequestPermission()) {
permissionCallback?.invoke(it)
permissionCallback = null
}
private var contentCallback: ((Uri) -> Unit)? = null
private val getContent = registerForActivityResult(GetContent()) {
if (it != null) contentCallback?.invoke(it)
contentCallback = null
}
override fun applyOverrideConfiguration(config: Configuration?) {
@ -57,72 +50,23 @@ abstract class BaseActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
}
fun withPermission(permission: String, builder: PermissionRequestBuilder.() -> Unit) {
val request = PermissionRequestBuilder().apply(builder).build()
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+
request.onSuccess()
callback(true)
return
}
if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) {
request.onSuccess()
} else {
val requestCode = newRequestCode
resultCallbacks[requestCode] = { result, _ ->
if (result > 0)
request.onSuccess()
else
request.onFailure()
}
ActivityCompat.requestPermissions(this, arrayOf(permission), requestCode)
}
permissionCallback = callback
requestPermission.launch(permission)
}
fun withExternalRW(builder: PermissionRequestBuilder.() -> Unit) {
withPermission(WRITE_EXTERNAL_STORAGE, builder = builder)
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
var success = true
for (res in grantResults) {
if (res != PackageManager.PERMISSION_GRANTED) {
success = false
break
}
}
resultCallbacks[requestCode]?.also {
resultCallbacks.remove(requestCode)
it(this, if (success) 1 else -1, null)
}
}
@CallSuper
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
resultCallbacks[requestCode]?.also { callback ->
resultCallbacks.remove(requestCode)
callback(this, resultCode, data)
}
}
fun startActivityForResult(intent: Intent, callback: ActivityResultCallback) {
val requestCode = newRequestCode
resultCallbacks[requestCode] = callback
try {
startActivityForResult(intent, requestCode)
} catch (e: ActivityNotFoundException) {
Utils.toast(R.string.app_not_found, Toast.LENGTH_SHORT)
}
fun getContent(type: String, callback: (Uri) -> Unit) {
contentCallback = callback
getContent.launch(type)
}
override fun recreate() {
startActivity(Intent().setComponent(intent.component))
finish()
}
}

View File

@ -1,40 +0,0 @@
package com.topjohnwu.magisk.core.base
typealias SimpleCallback = () -> Unit
typealias PermissionRationaleCallback = (List<String>) -> Unit
class PermissionRequestBuilder {
private var onSuccessCallback: SimpleCallback = {}
private var onFailureCallback: SimpleCallback = {}
private var onShowRationaleCallback: PermissionRationaleCallback = {}
fun onSuccess(callback: SimpleCallback) {
onSuccessCallback = callback
}
fun onFailure(callback: SimpleCallback) {
onFailureCallback = callback
}
fun onShowRationale(callback: PermissionRationaleCallback) {
onShowRationaleCallback = callback
}
fun build(): PermissionRequest {
return PermissionRequest(onSuccessCallback, onFailureCallback, onShowRationaleCallback)
}
}
class PermissionRequest(
private val onSuccessCallback: SimpleCallback,
private val onFailureCallback: SimpleCallback,
private val onShowRationaleCallback: PermissionRationaleCallback
) {
fun onSuccess() = onSuccessCallback()
fun onFailure() = onFailureCallback()
fun onShowRationale(permissions: List<String>) = onShowRationaleCallback(permissions)
}

View File

@ -1,9 +1,8 @@
package com.topjohnwu.magisk.events
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.view.View
import android.widget.Toast
import androidx.navigation.NavDirections
@ -11,18 +10,12 @@ 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.ActivityResultCallback
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.model.module.OnlineModule
import com.topjohnwu.magisk.events.dialog.MarkDownDialog
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.magisk.view.Shortcuts
class ViewActionEvent(val action: BaseActivity.() -> Unit) : ViewEvent(), ActivityExecutor {
override fun invoke(activity: BaseUIActivity<*, *>) = action(activity)
}
class OpenReadmeEvent(private val item: OnlineModule) : MarkDownDialog() {
override suspend fun getMarkdownText() = item.notes()
override fun build(dialog: MagiskDialog) {
@ -39,14 +32,7 @@ class PermissionEvent(
) : ViewEvent(), ActivityExecutor {
override fun invoke(activity: BaseUIActivity<*, *>) =
activity.withPermission(permission) {
onSuccess {
callback(true)
}
onFailure {
callback(false)
}
}
activity.withPermission(permission, callback)
}
class BackPressEvent : ViewEvent(), ActivityExecutor {
@ -75,12 +61,12 @@ class RecreateEvent : ViewEvent(), ActivityExecutor {
}
}
class MagiskInstallFileEvent(private val callback: ActivityResultCallback)
: ViewEvent(), ActivityExecutor {
class MagiskInstallFileEvent(
private val callback: (Uri) -> Unit
) : ViewEvent(), ActivityExecutor {
override fun invoke(activity: BaseUIActivity<*, *>) {
val intent = Intent(Intent.ACTION_GET_CONTENT).setType("*/*")
try {
activity.startActivityForResult(intent, callback)
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)
@ -106,17 +92,12 @@ class AddHomeIconEvent : ViewEvent(), ContextExecutor {
class SelectModuleEvent : ViewEvent(), FragmentExecutor {
override fun invoke(fragment: BaseUIFragment<*, *>) {
val intent = Intent(Intent.ACTION_GET_CONTENT).setType("application/zip")
try {
fragment.apply {
activity.startActivityForResult(intent) { code, intent ->
if (code == Activity.RESULT_OK && intent != null) {
intent.data?.also {
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)
}

View File

@ -1,6 +1,5 @@
package com.topjohnwu.magisk.ui.install
import android.app.Activity
import android.net.Uri
import androidx.databinding.Bindable
import androidx.lifecycle.viewModelScope
@ -42,10 +41,7 @@ class InstallViewModel(
set(value) = set(value, _method, { _method = it }, BR.method) {
when (it) {
R.id.method_patch -> {
MagiskInstallFileEvent { code, intent ->
if (code == Activity.RESULT_OK)
data = intent?.data
}.publish()
MagiskInstallFileEvent { uri -> data = uri }.publish()
}
R.id.method_inactive_slot -> {
SecondSlotWarningDialog().publish()