From 041f4802bf3bbd234dd1c7de93129c050d0a7408 Mon Sep 17 00:00:00 2001 From: vvb2060 Date: Wed, 13 Aug 2025 03:06:52 +0800 Subject: [PATCH 1/7] app: auto measure width --- .../magisk/databinding/RecyclerViewItems.kt | 4 --- .../magisk/databinding/RvItemAdapter.kt | 4 --- .../topjohnwu/magisk/ui/flash/ConsoleItem.kt | 28 +------------------ .../com/topjohnwu/magisk/ui/log/LogRvItem.kt | 18 +----------- .../src/main/res/layout/item_console_md2.xml | 2 +- .../src/main/res/layout/item_log_textview.xml | 2 +- 6 files changed, 4 insertions(+), 54 deletions(-) diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/databinding/RecyclerViewItems.kt b/app/apk/src/main/java/com/topjohnwu/magisk/databinding/RecyclerViewItems.kt index 7b26f832d0919..f2d64f987562a 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/databinding/RecyclerViewItems.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/databinding/RecyclerViewItems.kt @@ -16,10 +16,6 @@ interface ItemWrapper { val item: E } -interface ViewAwareItem { - fun onBind(binding: ViewDataBinding, recyclerView: RecyclerView) -} - interface DiffItem { fun itemSameAs(other: T): Boolean { diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/databinding/RvItemAdapter.kt b/app/apk/src/main/java/com/topjohnwu/magisk/databinding/RvItemAdapter.kt index 97ad66892e000..64c9017cecabc 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/databinding/RvItemAdapter.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/databinding/RvItemAdapter.kt @@ -52,10 +52,6 @@ class RvItemAdapter( } holder.binding.lifecycleOwner = lifecycleOwner holder.binding.executePendingBindings() - recyclerView?.let { - if (item is ViewAwareItem) - item.onBind(holder.binding, it) - } } override fun getItemCount() = items.size diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/flash/ConsoleItem.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/flash/ConsoleItem.kt index d0639d2d4fdc3..a0c3c95bf6a15 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/ui/flash/ConsoleItem.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/flash/ConsoleItem.kt @@ -1,38 +1,12 @@ package com.topjohnwu.magisk.ui.flash -import android.view.View -import android.widget.TextView -import androidx.core.view.updateLayoutParams -import androidx.databinding.ViewDataBinding -import androidx.recyclerview.widget.RecyclerView import com.topjohnwu.magisk.R import com.topjohnwu.magisk.databinding.DiffItem import com.topjohnwu.magisk.databinding.ItemWrapper import com.topjohnwu.magisk.databinding.RvItem -import com.topjohnwu.magisk.databinding.ViewAwareItem -import kotlin.math.max class ConsoleItem( override val item: String -) : RvItem(), ViewAwareItem, DiffItem, ItemWrapper { +) : RvItem(), DiffItem, ItemWrapper { override val layoutRes = R.layout.item_console_md2 - - private var parentWidth = -1 - - override fun onBind(binding: ViewDataBinding, recyclerView: RecyclerView) { - if (parentWidth < 0) - parentWidth = (recyclerView.parent as View).width - - val view = binding.root as TextView - view.measure(0, 0) - - // We want our recyclerView at least as wide as screen - val desiredWidth = max(view.measuredWidth, parentWidth) - - view.updateLayoutParams { width = desiredWidth } - - if (recyclerView.width < desiredWidth) { - recyclerView.requestLayout() - } - } } diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/log/LogRvItem.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/log/LogRvItem.kt index 21bffb382fa19..abf25fc2f590b 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/ui/log/LogRvItem.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/log/LogRvItem.kt @@ -1,28 +1,12 @@ package com.topjohnwu.magisk.ui.log -import androidx.databinding.ViewDataBinding -import androidx.recyclerview.widget.RecyclerView -import com.google.android.material.textview.MaterialTextView import com.topjohnwu.magisk.R import com.topjohnwu.magisk.databinding.DiffItem import com.topjohnwu.magisk.databinding.ItemWrapper import com.topjohnwu.magisk.databinding.ObservableRvItem -import com.topjohnwu.magisk.databinding.ViewAwareItem class LogRvItem( override val item: String -) : ObservableRvItem(), DiffItem, ItemWrapper, ViewAwareItem { - +) : ObservableRvItem(), DiffItem, ItemWrapper { override val layoutRes = R.layout.item_log_textview - - override fun onBind(binding: ViewDataBinding, recyclerView: RecyclerView) { - val view = binding.root as MaterialTextView - view.measure(0, 0) - val desiredWidth = view.measuredWidth - val layoutParams = view.layoutParams - layoutParams.width = desiredWidth - if (recyclerView.width < desiredWidth) { - recyclerView.requestLayout() - } - } } diff --git a/app/apk/src/main/res/layout/item_console_md2.xml b/app/apk/src/main/res/layout/item_console_md2.xml index f86c3d83b377f..fd8ed0bf1d705 100644 --- a/app/apk/src/main/res/layout/item_console_md2.xml +++ b/app/apk/src/main/res/layout/item_console_md2.xml @@ -11,7 +11,7 @@ Date: Mon, 29 Sep 2025 14:40:27 +0800 Subject: [PATCH 2/7] app: support download image and patch 1/2 --- .../topjohnwu/magisk/dialog/DownloadDialog.kt | 59 +++ .../magisk/ui/flash/FlashFragment.kt | 7 + .../magisk/ui/flash/FlashViewModel.kt | 8 + .../magisk/ui/install/InstallViewModel.kt | 5 + .../main/res/layout/fragment_install_md2.xml | 10 +- app/buildSrc/build.gradle.kts | 1 + app/core/build.gradle.kts | 20 + app/core/proguard-rules.pro | 3 + .../java/com/topjohnwu/magisk/core/Const.kt | 1 + .../magisk/core/tasks/MagiskInstaller.kt | 64 +++ .../magisk/core/utils/MediaStoreUtils.kt | 2 + app/core/src/main/proto/update_metadata.proto | 445 ++++++++++++++++++ app/core/src/main/res/values/strings.xml | 3 + app/gradle/libs.versions.toml | 5 + 14 files changed, 632 insertions(+), 1 deletion(-) create mode 100644 app/apk/src/main/java/com/topjohnwu/magisk/dialog/DownloadDialog.kt create mode 100644 app/core/src/main/proto/update_metadata.proto diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/dialog/DownloadDialog.kt b/app/apk/src/main/java/com/topjohnwu/magisk/dialog/DownloadDialog.kt new file mode 100644 index 0000000000000..d8d48f6f27d91 --- /dev/null +++ b/app/apk/src/main/java/com/topjohnwu/magisk/dialog/DownloadDialog.kt @@ -0,0 +1,59 @@ +package com.topjohnwu.magisk.dialog + +import android.net.Uri +import android.text.InputType +import android.widget.EditText +import androidx.core.net.toUri +import com.topjohnwu.magisk.core.R +import com.topjohnwu.magisk.events.DialogBuilder +import com.topjohnwu.magisk.view.MagiskDialog + +class DownloadDialog(private val callback: (Uri) -> Unit) : DialogBuilder { + + override fun build(dialog: MagiskDialog) { + val editText = EditText(dialog.context).apply { + inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_URI + hint = context.getString(R.string.download_dialog_msg) + requestFocus() + } + + dialog.apply { + setTitle(R.string.download_dialog_title) + setView(editText) + setButton(MagiskDialog.ButtonType.POSITIVE) { + text = android.R.string.ok + onClick { + val url = editText.text.toString().trim() + isValidUrl(url)?.let { + doNotDismiss = false + callback(it) + } ?: run { + doNotDismiss = true + editText.error = context.getString(R.string.download_dialog_title) + } + } + } + setButton(MagiskDialog.ButtonType.NEGATIVE) { + text = android.R.string.cancel + } + setCancelable(true) + } + } + + private fun isValidUrl(url: String): Uri? { + if (url.isEmpty()) { + return null + } + val uri = url.toUri() + if (!uri.scheme.equals("https", ignoreCase = true)) { + return null + } + if (uri.host.isNullOrEmpty()) { + return null + } + if (uri.path.isNullOrEmpty()) { + return null + } + return uri + } +} diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/flash/FlashFragment.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/flash/FlashFragment.kt index 7231a9b9fda32..fd694cfe40ef1 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/ui/flash/FlashFragment.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/flash/FlashFragment.kt @@ -127,6 +127,13 @@ class FlashFragment : BaseFragment(), MenuProvider { additionalData = uri ) + // Downloading is understood as downloading file then patch */ + + fun download(uri: Uri) = MainDirections.actionFlashFragment( + action = Const.Value.DOWNLOAD, + additionalData = uri + ) + /* Uninstalling is understood as removing magisk entirely */ fun uninstall() = MainDirections.actionFlashFragment( diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt index 56704d869c579..661f40dea84d0 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt @@ -1,5 +1,6 @@ package com.topjohnwu.magisk.ui.flash +import android.os.Build import android.view.MenuItem import androidx.databinding.Bindable import androidx.databinding.ObservableArrayList @@ -80,6 +81,13 @@ class FlashViewModel : BaseViewModel() { showReboot = false MagiskInstaller.Patch(uri, outItems, logItems).exec() } + Const.Value.DOWNLOAD -> { + if (uri == null || Build.VERSION.SDK_INT < 29) { + return@launch + } + showReboot = false + MagiskInstaller.Download(uri.toString(), outItems, logItems).exec() + } else -> { back() return@launch diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/install/InstallViewModel.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/install/InstallViewModel.kt index 6508ce4e16428..b6a64dffd3470 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/ui/install/InstallViewModel.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/install/InstallViewModel.kt @@ -21,6 +21,7 @@ import com.topjohnwu.magisk.core.base.ContentResultCallback import com.topjohnwu.magisk.core.ktx.toast import com.topjohnwu.magisk.core.repository.NetworkService import com.topjohnwu.magisk.databinding.set +import com.topjohnwu.magisk.dialog.DownloadDialog import com.topjohnwu.magisk.dialog.SecondSlotWarningDialog import com.topjohnwu.magisk.events.GetContentEvent import com.topjohnwu.magisk.ui.flash.FlashFragment @@ -54,6 +55,9 @@ class InstallViewModel(svc: NetworkService, markwon: Markwon) : BaseViewModel() R.id.method_patch -> { GetContentEvent("*/*", UriCallback()).publish() } + R.id.method_download -> { + DownloadDialog { url -> uri.value = url }.show() + } R.id.method_inactive_slot -> { SecondSlotWarningDialog().show() } @@ -92,6 +96,7 @@ class InstallViewModel(svc: NetworkService, markwon: Markwon) : BaseViewModel() fun install() { when (method) { R.id.method_patch -> FlashFragment.patch(data.value!!).navigate(true) + R.id.method_download -> FlashFragment.download(data.value!!).navigate(true) R.id.method_direct -> FlashFragment.flash(false).navigate(true) R.id.method_inactive_slot -> FlashFragment.flash(true).navigate(true) else -> error("Unknown value") diff --git a/app/apk/src/main/res/layout/fragment_install_md2.xml b/app/apk/src/main/res/layout/fragment_install_md2.xml index 7a7364379b650..5305a87a2e8a2 100644 --- a/app/apk/src/main/res/layout/fragment_install_md2.xml +++ b/app/apk/src/main/res/layout/fragment_install_md2.xml @@ -161,7 +161,7 @@