mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-22 07:57:39 +00:00
Add action.sh
for user to manually trigger modules' functionality from app
This commit is contained in:
parent
a9f8c20703
commit
fbebb6ac10
@ -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<FragmentActionMd2Binding>(), MenuProvider {
|
||||||
|
|
||||||
|
override val layoutRes = R.layout.fragment_action_md2
|
||||||
|
override val viewModel by viewModel<ActionViewModel>()
|
||||||
|
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
|
||||||
|
}
|
@ -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<State> get() = _state
|
||||||
|
val running = state.map { it == State.RUNNING }
|
||||||
|
|
||||||
|
val items = ObservableArrayList<ConsoleItem>()
|
||||||
|
lateinit var args: ActionFragmentArgs
|
||||||
|
|
||||||
|
private val logItems = mutableListOf<String>().synchronized()
|
||||||
|
private val outItems = object : CallbackList<String>() {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -25,6 +25,7 @@ class LocalModuleRvItem(
|
|||||||
override val layoutRes = R.layout.item_module_md2
|
override val layoutRes = R.layout.item_module_md2
|
||||||
|
|
||||||
val showNotice: Boolean
|
val showNotice: Boolean
|
||||||
|
val showAction: Boolean
|
||||||
val noticeText: TextHolder
|
val noticeText: TextHolder
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -35,6 +36,7 @@ class LocalModuleRvItem(
|
|||||||
showNotice = zygiskUnloaded ||
|
showNotice = zygiskUnloaded ||
|
||||||
(Info.isZygiskEnabled && isRiru) ||
|
(Info.isZygiskEnabled && isRiru) ||
|
||||||
(!Info.isZygiskEnabled && isZygisk)
|
(!Info.isZygiskEnabled && isZygisk)
|
||||||
|
showAction = item.hasAction && !showNotice
|
||||||
noticeText =
|
noticeText =
|
||||||
when {
|
when {
|
||||||
zygiskUnloaded -> CoreR.string.zygisk_module_unloaded.asText()
|
zygiskUnloaded -> CoreR.string.zygisk_module_unloaded.asText()
|
||||||
|
@ -4,8 +4,10 @@ import android.net.Uri
|
|||||||
import androidx.databinding.Bindable
|
import androidx.databinding.Bindable
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
|
import com.topjohnwu.magisk.MainDirections
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.arch.AsyncLoadViewModel
|
import com.topjohnwu.magisk.arch.AsyncLoadViewModel
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.core.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.magisk.core.base.ContentResultCallback
|
import com.topjohnwu.magisk.core.base.ContentResultCallback
|
||||||
import com.topjohnwu.magisk.core.model.module.LocalModule
|
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 {
|
companion object {
|
||||||
private val uri = MutableLiveData<Uri?>()
|
private val uri = MutableLiveData<Uri?>()
|
||||||
}
|
}
|
||||||
|
5
app/apk/src/main/res/drawable/ic_action_md2.xml
Normal file
5
app/apk/src/main/res/drawable/ic_action_md2.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M8,5v14l11,-7z"/>
|
||||||
|
|
||||||
|
</vector>
|
68
app/apk/src/main/res/layout/fragment_action_md2.xml
Normal file
68
app/apk/src/main/res/layout/fragment_action_md2.xml
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="viewModel"
|
||||||
|
type="com.topjohnwu.magisk.ui.module.ActionViewModel" />
|
||||||
|
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<HorizontalScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="@dimen/internal_action_bar_size"
|
||||||
|
app:layout_fitsSystemWindowsInsets="top"
|
||||||
|
tools:layout_marginTop="@dimen/internal_action_bar_size">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/flash_content"
|
||||||
|
scrollToLast="@{true}"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:fitsSystemWindowsInsets="start|end|bottom"
|
||||||
|
app:items="@{viewModel.items}"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||||
|
tools:listitem="@layout/item_console_md2" />
|
||||||
|
|
||||||
|
</HorizontalScrollView>
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
|
android:id="@+id/close_btn"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_margin="@dimen/l1"
|
||||||
|
android:layout_marginBottom="@dimen/l1"
|
||||||
|
android:clickable="true"
|
||||||
|
android:enabled="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:text="@string/close"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="?colorOnPrimary"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:backgroundTint="?colorPrimary"
|
||||||
|
app:icon="@drawable/ic_close_md2"
|
||||||
|
app:iconTint="?colorOnPrimary"
|
||||||
|
app:layout_fitsSystemWindowsInsets="bottom" />
|
||||||
|
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
android:id="@+id/snackbar_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:fitsSystemWindowsInsets="top|bottom" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
|
</layout>
|
@ -189,12 +189,32 @@
|
|||||||
android:textColor="?colorError"
|
android:textColor="?colorError"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/module_remove"
|
app:layout_constraintBottom_toBottomOf="@+id/module_remove"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/bottom_bar_barrier"
|
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"
|
app:layout_constraintTop_toTopOf="@+id/module_remove"
|
||||||
tools:lines="2"
|
tools:lines="2"
|
||||||
tools:text="@tools:sample/lorem/random"
|
tools:text="@tools:sample/lorem/random"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/module_config"
|
||||||
|
style="@style/WidgetFoundation.Button.Text"
|
||||||
|
goneUnless="@{item.showAction}"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clickable="true"
|
||||||
|
android:enabled="@{item.enabled}"
|
||||||
|
android:focusable="true"
|
||||||
|
android:onClick="@{() -> viewModel.runAction(item.item.id, item.item.name)}"
|
||||||
|
android:text="@string/module_action"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:icon="@drawable/ic_action_md2"
|
||||||
|
app:iconGravity="textStart"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/module_remove"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/module_remove"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:srcCompat="@drawable/ic_download_md2" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
@ -53,6 +53,21 @@
|
|||||||
|
|
||||||
</fragment>
|
</fragment>
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/actionFragment"
|
||||||
|
android:name="com.topjohnwu.magisk.ui.module.ActionFragment"
|
||||||
|
android:label="ActionFragment"
|
||||||
|
tools:layout="@layout/fragment_action_md2" >
|
||||||
|
|
||||||
|
<argument
|
||||||
|
android:name="id"
|
||||||
|
app:argType="string" />
|
||||||
|
|
||||||
|
<argument
|
||||||
|
android:name="name"
|
||||||
|
app:argType="string" />
|
||||||
|
</fragment>
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/installFragment"
|
android:id="@+id/installFragment"
|
||||||
android:name="com.topjohnwu.magisk.ui.install.InstallFragment"
|
android:name="com.topjohnwu.magisk.ui.install.InstallFragment"
|
||||||
@ -152,4 +167,12 @@
|
|||||||
app:popEnterAnim="@anim/fragment_enter_pop"
|
app:popEnterAnim="@anim/fragment_enter_pop"
|
||||||
app:popExitAnim="@anim/fragment_exit_pop" />
|
app:popExitAnim="@anim/fragment_exit_pop" />
|
||||||
|
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_actionFragment"
|
||||||
|
app:destination="@id/actionFragment"
|
||||||
|
app:enterAnim="@anim/fragment_enter"
|
||||||
|
app:exitAnim="@anim/fragment_exit"
|
||||||
|
app:popEnterAnim="@anim/fragment_enter_pop"
|
||||||
|
app:popExitAnim="@anim/fragment_exit_pop" />
|
||||||
|
|
||||||
</navigation>
|
</navigation>
|
||||||
|
@ -37,6 +37,7 @@ data class LocalModule(
|
|||||||
val isRiru: Boolean get() = (id == "riru-core") || riruFolder.exists()
|
val isRiru: Boolean get() = (id == "riru-core") || riruFolder.exists()
|
||||||
val isZygisk: Boolean get() = zygiskFolder.exists()
|
val isZygisk: Boolean get() = zygiskFolder.exists()
|
||||||
val zygiskUnloaded: Boolean get() = unloaded.exists()
|
val zygiskUnloaded: Boolean get() = unloaded.exists()
|
||||||
|
val hasAction: Boolean;
|
||||||
|
|
||||||
var enable: Boolean
|
var enable: Boolean
|
||||||
get() = !disableFile.exists()
|
get() = !disableFile.exists()
|
||||||
@ -100,6 +101,8 @@ data class LocalModule(
|
|||||||
if (name.isEmpty()) {
|
if (name.isEmpty()) {
|
||||||
name = id
|
name = id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasAction = RootUtils.fs.getFile(path, "action.sh").exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun fetch(): Boolean {
|
suspend fun fetch(): Boolean {
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
package com.topjohnwu.magisk.core.tasks
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.core.net.toFile
|
||||||
|
import com.topjohnwu.magisk.core.AppContext
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||||
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName
|
||||||
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
|
||||||
|
import com.topjohnwu.magisk.core.utils.unzip
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
open class RunAction(
|
||||||
|
private val module: String,
|
||||||
|
private val console: MutableList<String>,
|
||||||
|
private val logs: MutableList<String>
|
||||||
|
) {
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private suspend fun run(): Boolean {
|
||||||
|
return Shell.cmd("run_action \'$module\'").to(console, logs).exec().isSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun exec() = withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
if (!run()) {
|
||||||
|
console.add("! Run action failed")
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Timber.e(e)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -109,6 +109,7 @@
|
|||||||
<string name="reboot_safe_mode">Safe mode</string>
|
<string name="reboot_safe_mode">Safe mode</string>
|
||||||
<string name="module_version_author">%1$s by %2$s</string>
|
<string name="module_version_author">%1$s by %2$s</string>
|
||||||
<string name="module_state_remove">Remove</string>
|
<string name="module_state_remove">Remove</string>
|
||||||
|
<string name="module_action">Action</string>
|
||||||
<string name="module_state_restore">Restore</string>
|
<string name="module_state_restore">Restore</string>
|
||||||
<string name="module_action_install_external">Install from storage</string>
|
<string name="module_action_install_external">Install from storage</string>
|
||||||
<string name="update_available">Update Available</string>
|
<string name="update_available">Update Available</string>
|
||||||
@ -211,8 +212,10 @@
|
|||||||
<string name="repo_install_title">Install %1$s %2$s(%3$d)</string>
|
<string name="repo_install_title">Install %1$s %2$s(%3$d)</string>
|
||||||
<string name="download">Download</string>
|
<string name="download">Download</string>
|
||||||
<string name="reboot">Reboot</string>
|
<string name="reboot">Reboot</string>
|
||||||
|
<string name="close">Close</string>
|
||||||
<string name="release_notes">Release notes</string>
|
<string name="release_notes">Release notes</string>
|
||||||
<string name="flashing">Flashing…</string>
|
<string name="flashing">Flashing…</string>
|
||||||
|
<string name="running">Running...</string>
|
||||||
<string name="done">Done!</string>
|
<string name="done">Done!</string>
|
||||||
<string name="failure">Failed!</string>
|
<string name="failure">Failed!</string>
|
||||||
<string name="hide_app_title">Hiding the Magisk app…</string>
|
<string name="hide_app_title">Hiding the Magisk app…</string>
|
||||||
|
@ -54,6 +54,7 @@ A Magisk module is a folder placed in `/data/adb/modules` with the structure bel
|
|||||||
│ ├── post-fs-data.sh <--- This script will be executed in post-fs-data
|
│ ├── post-fs-data.sh <--- This script will be executed in post-fs-data
|
||||||
│ ├── service.sh <--- This script will be executed in late_start service
|
│ ├── service.sh <--- This script will be executed in late_start service
|
||||||
| ├── uninstall.sh <--- This script will be executed when Magisk removes your module
|
| ├── uninstall.sh <--- This script will be executed when Magisk removes your module
|
||||||
|
| ├── action.sh <--- This script will be executed when user click the action button in Magisk app
|
||||||
│ ├── system.prop <--- Properties in this file will be loaded as system properties by resetprop
|
│ ├── system.prop <--- Properties in this file will be loaded as system properties by resetprop
|
||||||
│ ├── sepolicy.rule <--- Additional custom sepolicy rules
|
│ ├── sepolicy.rule <--- Additional custom sepolicy rules
|
||||||
│ │
|
│ │
|
||||||
|
@ -189,6 +189,13 @@ printvar() {
|
|||||||
eval echo $1=\$$1
|
eval echo $1=\$$1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
run_action() {
|
||||||
|
local MODID="$1"
|
||||||
|
cd "/data/adb/modules/$MODID"
|
||||||
|
ASH_STANDALONE=1 sh ./action.sh
|
||||||
|
cd /
|
||||||
|
}
|
||||||
|
|
||||||
##########################
|
##########################
|
||||||
# Non-root util_functions
|
# Non-root util_functions
|
||||||
##########################
|
##########################
|
||||||
|
Loading…
x
Reference in New Issue
Block a user