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 package com.topjohnwu.magisk.arch
import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContract
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
@ -23,12 +25,13 @@ import java.util.concurrent.CountDownLatch
abstract class BaseMainActivity<VM : BaseViewModel, Binding : ViewDataBinding> abstract class BaseMainActivity<VM : BaseViewModel, Binding : ViewDataBinding>
: BaseUIActivity<VM, Binding>() { : BaseUIActivity<VM, Binding>() {
private val latch = CountDownLatch(1)
companion object { companion object {
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)
@ -107,19 +110,24 @@ abstract class BaseMainActivity<VM : BaseViewModel, Binding : ViewDataBinding>
if (Config.suManager.isNotEmpty()) if (Config.suManager.isNotEmpty())
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) {
uninstallApp(pkg) uninstallPkg.launch(pkg)
} // Wait for the uninstallation to finish
}
@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()
}
latch.await() 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() PermissionEvent(permission, callback).publish()
} }
fun withExternalRW(callback: () -> Unit) { inline fun withExternalRW(crossinline callback: () -> Unit) {
withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) { withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) {
if (!it) { if (!it) {
SnackbarEvent(R.string.external_rw_permission_denied).publish() SnackbarEvent(R.string.external_rw_permission_denied).publish()

View File

@ -1,39 +1,32 @@
package com.topjohnwu.magisk.core.base package com.topjohnwu.magisk.core.base
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts.GetContent
import androidx.annotation.CallSuper import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
import androidx.appcompat.app.AppCompatActivity 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.isRunningAsStub
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 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() { abstract class BaseActivity : AppCompatActivity() {
private val resultCallbacks by lazy { SparseArrayCompat<ActivityResultCallback>() } private var permissionCallback: ((Boolean) -> Unit)? = null
private val newRequestCode: Int get() { private val requestPermission = registerForActivityResult(RequestPermission()) {
var requestCode: Int permissionCallback?.invoke(it)
do { permissionCallback = null
requestCode = Random.nextInt(0, 1 shl 15) }
} while (resultCallbacks.containsKey(requestCode))
return requestCode private var contentCallback: ((Uri) -> Unit)? = null
private val getContent = registerForActivityResult(GetContent()) {
if (it != null) contentCallback?.invoke(it)
contentCallback = null
} }
override fun applyOverrideConfiguration(config: Configuration?) { override fun applyOverrideConfiguration(config: Configuration?) {
@ -57,72 +50,23 @@ abstract class BaseActivity : AppCompatActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
} }
fun withPermission(permission: String, builder: PermissionRequestBuilder.() -> Unit) { fun withPermission(permission: String, callback: (Boolean) -> Unit) {
val request = PermissionRequestBuilder().apply(builder).build()
if (permission == WRITE_EXTERNAL_STORAGE && Build.VERSION.SDK_INT >= 30) { if (permission == WRITE_EXTERNAL_STORAGE && Build.VERSION.SDK_INT >= 30) {
// We do not need external rw on 30+ // We do not need external rw on 30+
request.onSuccess() callback(true)
return return
} }
permissionCallback = callback
if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) { requestPermission.launch(permission)
request.onSuccess()
} else {
val requestCode = newRequestCode
resultCallbacks[requestCode] = { result, _ ->
if (result > 0)
request.onSuccess()
else
request.onFailure()
}
ActivityCompat.requestPermissions(this, arrayOf(permission), requestCode)
}
} }
fun withExternalRW(builder: PermissionRequestBuilder.() -> Unit) { fun getContent(type: String, callback: (Uri) -> Unit) {
withPermission(WRITE_EXTERNAL_STORAGE, builder = builder) contentCallback = callback
} getContent.launch(type)
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)
}
} }
override fun recreate() { override fun recreate() {
startActivity(Intent().setComponent(intent.component)) startActivity(Intent().setComponent(intent.component))
finish() 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 package com.topjohnwu.magisk.events
import android.app.Activity
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Intent import android.net.Uri
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
@ -11,18 +10,12 @@ 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.core.base.ActivityResultCallback
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.model.module.OnlineModule import com.topjohnwu.magisk.core.model.module.OnlineModule
import com.topjohnwu.magisk.events.dialog.MarkDownDialog import com.topjohnwu.magisk.events.dialog.MarkDownDialog
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.magisk.view.Shortcuts 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() { class OpenReadmeEvent(private val item: OnlineModule) : MarkDownDialog() {
override suspend fun getMarkdownText() = item.notes() override suspend fun getMarkdownText() = item.notes()
override fun build(dialog: MagiskDialog) { override fun build(dialog: MagiskDialog) {
@ -39,14 +32,7 @@ class PermissionEvent(
) : ViewEvent(), ActivityExecutor { ) : ViewEvent(), ActivityExecutor {
override fun invoke(activity: BaseUIActivity<*, *>) = override fun invoke(activity: BaseUIActivity<*, *>) =
activity.withPermission(permission) { activity.withPermission(permission, callback)
onSuccess {
callback(true)
}
onFailure {
callback(false)
}
}
} }
class BackPressEvent : ViewEvent(), ActivityExecutor { class BackPressEvent : ViewEvent(), ActivityExecutor {
@ -75,12 +61,12 @@ class RecreateEvent : ViewEvent(), ActivityExecutor {
} }
} }
class MagiskInstallFileEvent(private val callback: ActivityResultCallback) class MagiskInstallFileEvent(
: ViewEvent(), ActivityExecutor { private val callback: (Uri) -> Unit
) : ViewEvent(), ActivityExecutor {
override fun invoke(activity: BaseUIActivity<*, *>) { override fun invoke(activity: BaseUIActivity<*, *>) {
val intent = Intent(Intent.ACTION_GET_CONTENT).setType("*/*")
try { try {
activity.startActivityForResult(intent, callback) activity.getContent("*/*", callback)
Utils.toast(R.string.patch_file_msg, Toast.LENGTH_LONG) Utils.toast(R.string.patch_file_msg, Toast.LENGTH_LONG)
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {
Utils.toast(R.string.app_not_found, Toast.LENGTH_SHORT) Utils.toast(R.string.app_not_found, Toast.LENGTH_SHORT)
@ -106,17 +92,12 @@ class AddHomeIconEvent : ViewEvent(), ContextExecutor {
class SelectModuleEvent : ViewEvent(), FragmentExecutor { class SelectModuleEvent : ViewEvent(), FragmentExecutor {
override fun invoke(fragment: BaseUIFragment<*, *>) { override fun invoke(fragment: BaseUIFragment<*, *>) {
val intent = Intent(Intent.ACTION_GET_CONTENT).setType("application/zip")
try { try {
fragment.apply { fragment.apply {
activity.startActivityForResult(intent) { code, intent -> activity.getContent("application/zip") {
if (code == Activity.RESULT_OK && intent != null) {
intent.data?.also {
MainDirections.actionFlashFragment(Const.Value.FLASH_ZIP, it).navigate() MainDirections.actionFlashFragment(Const.Value.FLASH_ZIP, it).navigate()
} }
} }
}
}
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {
Utils.toast(R.string.app_not_found, Toast.LENGTH_SHORT) Utils.toast(R.string.app_not_found, Toast.LENGTH_SHORT)
} }

View File

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