From fbebb6ac10090b064cc7868b9d0048c587678891 Mon Sep 17 00:00:00 2001 From: LoveSy Date: Sun, 1 Sep 2024 23:08:38 +0800 Subject: [PATCH] Add `action.sh` for user to manually trigger modules' functionality from app --- .../magisk/ui/module/ActionFragment.kt | 98 +++++++++++++++++++ .../magisk/ui/module/ActionViewModel.kt | 84 ++++++++++++++++ .../magisk/ui/module/ModuleRvItem.kt | 2 + .../magisk/ui/module/ModuleViewModel.kt | 6 ++ .../src/main/res/drawable/ic_action_md2.xml | 5 + .../main/res/layout/fragment_action_md2.xml | 68 +++++++++++++ .../src/main/res/layout/item_module_md2.xml | 22 ++++- app/apk/src/main/res/navigation/main.xml | 23 +++++ .../magisk/core/model/module/LocalModule.kt | 3 + .../topjohnwu/magisk/core/tasks/RunAction.kt | 42 ++++++++ app/core/src/main/res/values/strings.xml | 3 + docs/guides.md | 1 + scripts/app_functions.sh | 7 ++ 13 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 app/apk/src/main/java/com/topjohnwu/magisk/ui/module/ActionFragment.kt create mode 100644 app/apk/src/main/java/com/topjohnwu/magisk/ui/module/ActionViewModel.kt create mode 100644 app/apk/src/main/res/drawable/ic_action_md2.xml create mode 100644 app/apk/src/main/res/layout/fragment_action_md2.xml create mode 100644 app/core/src/main/java/com/topjohnwu/magisk/core/tasks/RunAction.kt diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/module/ActionFragment.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/module/ActionFragment.kt new file mode 100644 index 000000000..4117aba99 --- /dev/null +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/module/ActionFragment.kt @@ -0,0 +1,98 @@ +package com.topjohnwu.magisk.ui.module + +import android.annotation.SuppressLint +import android.content.pm.ActivityInfo +import android.os.Bundle +import android.view.KeyEvent +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import androidx.core.view.MenuProvider +import androidx.core.view.isVisible +import com.topjohnwu.magisk.R +import com.topjohnwu.magisk.arch.BaseFragment +import com.topjohnwu.magisk.arch.viewModel +import com.topjohnwu.magisk.databinding.FragmentActionMd2Binding +import com.topjohnwu.magisk.ui.flash.FlashViewModel +import timber.log.Timber +import com.topjohnwu.magisk.core.R as CoreR + +class ActionFragment : BaseFragment(), MenuProvider { + + override val layoutRes = R.layout.fragment_action_md2 + override val viewModel by viewModel() + override val snackbarView: View get() = binding.snackbarContainer + + private var defaultOrientation = -1 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + viewModel.args = ActionFragmentArgs.fromBundle(requireArguments()) + } + + override fun onStart() { + super.onStart() + activity?.setTitle(viewModel.args.name) + binding.closeBtn.setOnClickListener { + activity?.onBackPressed(); + } + + viewModel.state.observe(this) { + activity?.supportActionBar?.setSubtitle( + when (it) { + ActionViewModel.State.RUNNING -> CoreR.string.running + ActionViewModel.State.SUCCESS -> CoreR.string.done + ActionViewModel.State.FAILED -> CoreR.string.failure + } + ) + if (it != ActionViewModel.State.RUNNING) { + binding.closeBtn.apply { + if (!this.isVisible) this.show() + if (!this.isFocused) this.requestFocus() + } + } + } + } + + override fun onCreateMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.menu_flash, menu) + } + + override fun onMenuItemSelected(item: MenuItem): Boolean { + return viewModel.onMenuItemClicked(item) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + defaultOrientation = activity?.requestedOrientation ?: -1 + activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR + if (savedInstanceState == null) { + viewModel.startRunAction() + } + } + + @SuppressLint("WrongConstant") + override fun onDestroyView() { + if (defaultOrientation != -1) { + activity?.requestedOrientation = defaultOrientation + } + super.onDestroyView() + } + + override fun onKeyEvent(event: KeyEvent): Boolean { + return when (event.keyCode) { + KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> true + + else -> false + } + } + + override fun onBackPressed(): Boolean { + if (!binding.closeBtn.isVisible) return true + return super.onBackPressed() + } + + override fun onPreBind(binding: FragmentActionMd2Binding) = Unit +} diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/module/ActionViewModel.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/module/ActionViewModel.kt new file mode 100644 index 000000000..6f1f78c6d --- /dev/null +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/module/ActionViewModel.kt @@ -0,0 +1,84 @@ +package com.topjohnwu.magisk.ui.module + +import android.view.MenuItem +import androidx.databinding.Bindable +import androidx.databinding.ObservableArrayList +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.map +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.ktx.synchronized +import com.topjohnwu.magisk.core.ktx.timeFormatStandard +import com.topjohnwu.magisk.core.ktx.toTime +import com.topjohnwu.magisk.core.tasks.RunAction +import com.topjohnwu.magisk.core.utils.MediaStoreUtils +import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream +import com.topjohnwu.magisk.databinding.set +import com.topjohnwu.magisk.events.SnackbarEvent +import com.topjohnwu.magisk.ui.flash.ConsoleItem +import com.topjohnwu.superuser.CallbackList +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class ActionViewModel : BaseViewModel() { + + enum class State { + RUNNING, SUCCESS, FAILED + } + + private val _state = MutableLiveData(State.RUNNING) + val state: LiveData get() = _state + val running = state.map { it == State.RUNNING } + + val items = ObservableArrayList() + lateinit var args: ActionFragmentArgs + + private val logItems = mutableListOf().synchronized() + private val outItems = object : CallbackList() { + override fun onAddElement(e: String?) { + e ?: return + items.add(ConsoleItem(e)) + logItems.add(e) + } + } + + fun startRunAction() { + viewModelScope.launch { + onResult(RunAction(args.id, outItems, logItems).exec()) + } + } + + private fun onResult(success: Boolean) { + _state.value = if (success) State.SUCCESS else State.FAILED + } + + fun onMenuItemClicked(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_save -> savePressed() + } + return true + } + + private fun savePressed() = withExternalRW { + viewModelScope.launch(Dispatchers.IO) { + val name = "%s_action_log_%s.log".format( + args.name, + System.currentTimeMillis().toTime(timeFormatStandard) + ) + val file = MediaStoreUtils.getFile(name) + file.uri.outputStream().bufferedWriter().use { writer -> + synchronized(logItems) { + logItems.forEach { + writer.write(it) + writer.newLine() + } + } + } + SnackbarEvent(file.toString()).publish() + } + } +} diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/module/ModuleRvItem.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/module/ModuleRvItem.kt index a0c6d4535..97c164db0 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/ui/module/ModuleRvItem.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/module/ModuleRvItem.kt @@ -25,6 +25,7 @@ class LocalModuleRvItem( override val layoutRes = R.layout.item_module_md2 val showNotice: Boolean + val showAction: Boolean val noticeText: TextHolder init { @@ -35,6 +36,7 @@ class LocalModuleRvItem( showNotice = zygiskUnloaded || (Info.isZygiskEnabled && isRiru) || (!Info.isZygiskEnabled && isZygisk) + showAction = item.hasAction && !showNotice noticeText = when { zygiskUnloaded -> CoreR.string.zygisk_module_unloaded.asText() diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt index 4ee7c9369..863206338 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt @@ -4,8 +4,10 @@ import android.net.Uri import androidx.databinding.Bindable import androidx.lifecycle.MutableLiveData import com.topjohnwu.magisk.BR +import com.topjohnwu.magisk.MainDirections import com.topjohnwu.magisk.R import com.topjohnwu.magisk.arch.AsyncLoadViewModel +import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.base.ContentResultCallback import com.topjohnwu.magisk.core.model.module.LocalModule @@ -96,6 +98,10 @@ class ModuleViewModel : AsyncLoadViewModel() { } } + fun runAction(id: String, name: String) { + MainDirections.actionActionFragment(id, name).navigate() + } + companion object { private val uri = MutableLiveData() } diff --git a/app/apk/src/main/res/drawable/ic_action_md2.xml b/app/apk/src/main/res/drawable/ic_action_md2.xml new file mode 100644 index 000000000..6cb3671f4 --- /dev/null +++ b/app/apk/src/main/res/drawable/ic_action_md2.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/apk/src/main/res/layout/fragment_action_md2.xml b/app/apk/src/main/res/layout/fragment_action_md2.xml new file mode 100644 index 000000000..f50e50963 --- /dev/null +++ b/app/apk/src/main/res/layout/fragment_action_md2.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/apk/src/main/res/layout/item_module_md2.xml b/app/apk/src/main/res/layout/item_module_md2.xml index 5407a49c3..b33d221ca 100644 --- a/app/apk/src/main/res/layout/item_module_md2.xml +++ b/app/apk/src/main/res/layout/item_module_md2.xml @@ -189,12 +189,32 @@ android:textColor="?colorError" app:layout_constraintBottom_toBottomOf="@+id/module_remove" app:layout_constraintEnd_toStartOf="@+id/bottom_bar_barrier" - app:layout_constraintStart_toStartOf="parent" + app:layout_constraintStart_toEndOf="@id/module_config" app:layout_constraintTop_toTopOf="@+id/module_remove" tools:lines="2" tools:text="@tools:sample/lorem/random" tools:visibility="visible" /> +