From 1bdd6e1a9d3952c762b7f231e3142d3882b2f34f Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Fri, 5 Nov 2021 15:53:34 -0700 Subject: [PATCH] Migrate to Activity Result APIs --- .../topjohnwu/magisk/arch/BaseMainActivity.kt | 32 ++++--- .../topjohnwu/magisk/arch/BaseViewModel.kt | 2 +- .../magisk/core/base/BaseActivity.kt | 96 ++++--------------- .../core/base/PermissionRequestBuilder.kt | 40 -------- .../com/topjohnwu/magisk/events/ViewEvents.kt | 35 ++----- .../magisk/ui/install/InstallViewModel.kt | 6 +- 6 files changed, 50 insertions(+), 161 deletions(-) delete mode 100644 app/src/main/java/com/topjohnwu/magisk/core/base/PermissionRequestBuilder.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 bb4fe3021..c3f4981c7 100644 --- a/app/src/main/java/com/topjohnwu/magisk/arch/BaseMainActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/arch/BaseMainActivity.kt @@ -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 : BaseUIActivity() { - 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 if (Config.suManager.isNotEmpty()) Config.suManager = "" pkg ?: return - if (!Shell.su("(pm uninstall $pkg)& >/dev/null 2>&1").exec().isSuccess) - uninstallApp(pkg) + if (!Shell.su("(pm uninstall $pkg)& >/dev/null 2>&1").exec().isSuccess) { + uninstallPkg.launch(pkg) + // Wait for the uninstallation to finish + latch.await() + } } } - @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() + 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 } - latch.await() + + 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 975d55e97..14dc80a79 100644 --- a/app/src/main/java/com/topjohnwu/magisk/arch/BaseViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/arch/BaseViewModel.kt @@ -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() 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 b673ac2a9..c39cc1344 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,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() } - 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, 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() } - } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/base/PermissionRequestBuilder.kt b/app/src/main/java/com/topjohnwu/magisk/core/base/PermissionRequestBuilder.kt deleted file mode 100644 index d042368f3..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/core/base/PermissionRequestBuilder.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.topjohnwu.magisk.core.base - -typealias SimpleCallback = () -> Unit -typealias PermissionRationaleCallback = (List) -> 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) = onShowRationaleCallback(permissions) - -} 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 8476a7bc3..8baae5f2a 100644 --- a/app/src/main/java/com/topjohnwu/magisk/events/ViewEvents.kt +++ b/app/src/main/java/com/topjohnwu/magisk/events/ViewEvents.kt @@ -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,15 +92,10 @@ 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 { - MainDirections.actionFlashFragment(Const.Value.FLASH_ZIP, it).navigate() - } - } + activity.getContent("application/zip") { + MainDirections.actionFlashFragment(Const.Value.FLASH_ZIP, it).navigate() } } } catch (e: ActivityNotFoundException) { diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/install/InstallViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/install/InstallViewModel.kt index b51705656..59eab5e70 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/install/InstallViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/install/InstallViewModel.kt @@ -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()