Download Magisk Manager via new service
This commit is contained in:
parent
3d81f167ea
commit
85f5ff3c14
@ -76,8 +76,7 @@ object Const {
|
||||
const val ETAG_KEY = "ETag"
|
||||
// intents
|
||||
const val OPEN_SECTION = "section"
|
||||
const val INTENT_SET_NAME = "filename"
|
||||
const val INTENT_SET_LINK = "link"
|
||||
const val INTENT_SET_APP = "app_json"
|
||||
const val FLASH_ACTION = "action"
|
||||
const val FLASH_DATA = "additional_data"
|
||||
const val DISMISS_ID = "dismiss_id"
|
||||
|
@ -14,9 +14,9 @@ import com.topjohnwu.magisk.extensions.provide
|
||||
import com.topjohnwu.magisk.model.entity.internal.Configuration.*
|
||||
import com.topjohnwu.magisk.model.entity.internal.Configuration.Flash.Secondary
|
||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.Magisk
|
||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.Module
|
||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
|
||||
import com.topjohnwu.magisk.ui.flash.FlashActivity
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import java.io.File
|
||||
import kotlin.random.Random.Default.nextInt
|
||||
|
||||
@ -33,6 +33,7 @@ open class DownloadService : RemoteFileService() {
|
||||
override fun onFinished(file: File, subject: DownloadSubject, id: Int) = when (subject) {
|
||||
is Magisk -> onFinishedInternal(file, subject, id)
|
||||
is Module -> onFinishedInternal(file, subject, id)
|
||||
is Manager -> onFinishedInternal(file, subject, id)
|
||||
}
|
||||
|
||||
private fun onFinishedInternal(
|
||||
@ -55,6 +56,18 @@ open class DownloadService : RemoteFileService() {
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
private fun onFinishedInternal(
|
||||
file: File,
|
||||
subject: Manager,
|
||||
id: Int
|
||||
) {
|
||||
remove(id)
|
||||
when (subject.configuration) {
|
||||
is APK.Upgrade -> APKInstall.install(this, file)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
override fun NotificationCompat.Builder.addActions(
|
||||
@ -63,6 +76,7 @@ open class DownloadService : RemoteFileService() {
|
||||
) = when (subject) {
|
||||
is Magisk -> addActionsInternal(file, subject)
|
||||
is Module -> addActionsInternal(file, subject)
|
||||
is Manager -> addActionsInternal(file, subject)
|
||||
}
|
||||
|
||||
private fun NotificationCompat.Builder.addActionsInternal(
|
||||
@ -87,6 +101,14 @@ open class DownloadService : RemoteFileService() {
|
||||
else -> this
|
||||
}
|
||||
|
||||
private fun NotificationCompat.Builder.addActionsInternal(
|
||||
file: File,
|
||||
subject: Manager
|
||||
) = when (subject.configuration) {
|
||||
APK.Upgrade -> setContentIntent(APKInstall.installIntent(context, file))
|
||||
else -> this
|
||||
}
|
||||
|
||||
@Suppress("ReplaceSingleLineLet")
|
||||
private fun NotificationCompat.Builder.setContentIntent(intent: Intent) =
|
||||
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
|
||||
|
@ -0,0 +1,67 @@
|
||||
package com.topjohnwu.magisk.model.download
|
||||
|
||||
import android.content.ComponentName
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.ClassMap
|
||||
import com.topjohnwu.magisk.Config
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Restore
|
||||
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Upgrade
|
||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||
import com.topjohnwu.magisk.ui.SplashActivity
|
||||
import com.topjohnwu.magisk.utils.PatchAPK
|
||||
import com.topjohnwu.magisk.utils.RootUtils
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import dalvik.system.DexClassLoader
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
||||
private fun RemoteFileService.patchPackage(apk: File, id: Int): File {
|
||||
if (packageName != BuildConfig.APPLICATION_ID) {
|
||||
update(id) { notification ->
|
||||
notification.setProgress(0, 0, true)
|
||||
.setProgress(0, 0, true)
|
||||
.setContentTitle(getString(R.string.hide_manager_title))
|
||||
.setContentText("")
|
||||
}
|
||||
val patched = File(apk.parent, "patched.apk")
|
||||
try {
|
||||
// Try using the new APK to patch itself
|
||||
val loader = DexClassLoader(apk.path, apk.parent, null, ClassLoader.getSystemClassLoader())
|
||||
loader.loadClass("a.a")
|
||||
.getMethod("patchAPK", String::class.java, String::class.java, String::class.java)
|
||||
.invoke(null, apk.path, patched.path, packageName)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
// Fallback to use the current implementation
|
||||
PatchAPK.patch(apk.path, patched.path, packageName)
|
||||
}
|
||||
apk.delete()
|
||||
return patched
|
||||
} else {
|
||||
return apk
|
||||
}
|
||||
}
|
||||
|
||||
private fun RemoteFileService.restore(apk: File, id: Int): File {
|
||||
update(id) { notification ->
|
||||
notification.setProgress(0, 0, true)
|
||||
.setProgress(0, 0, true)
|
||||
.setContentTitle(getString(R.string.restore_img_msg))
|
||||
.setContentText("")
|
||||
}
|
||||
Config.export()
|
||||
// Make it world readable
|
||||
apk.setReadable(true, false)
|
||||
if (Shell.su("pm install $apk").exec().isSuccess)
|
||||
RootUtils.rmAndLaunch(packageName,
|
||||
ComponentName(BuildConfig.APPLICATION_ID,
|
||||
ClassMap.get<Class<*>>(SplashActivity::class.java).name))
|
||||
return apk
|
||||
}
|
||||
|
||||
fun RemoteFileService.handleAPK(apk: File, subject: DownloadSubject.Manager)
|
||||
= when (subject.configuration) {
|
||||
is Upgrade -> patchPackage(apk, subject.hashCode())
|
||||
is Restore -> restore(apk, subject.hashCode())
|
||||
}
|
@ -27,7 +27,7 @@ abstract class NotificationService : Service() {
|
||||
|
||||
// --
|
||||
|
||||
protected fun update(
|
||||
fun update(
|
||||
id: Int,
|
||||
body: (NotificationCompat.Builder) -> Unit = {}
|
||||
) {
|
||||
|
@ -11,8 +11,7 @@ import com.topjohnwu.magisk.extensions.firstMap
|
||||
import com.topjohnwu.magisk.extensions.get
|
||||
import com.topjohnwu.magisk.extensions.writeTo
|
||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.Magisk
|
||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.Module
|
||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
|
||||
import com.topjohnwu.magisk.utils.ProgInputStream
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import com.topjohnwu.superuser.ShellUtils
|
||||
@ -80,6 +79,11 @@ abstract class RemoteFileService : NotificationService() {
|
||||
.map { stream.toModule(subject.file, it.byteStream()); subject.file }
|
||||
else -> Single.fromCallable { stream.writeTo(subject.file); subject.file }
|
||||
}
|
||||
}.map {
|
||||
when (subject) {
|
||||
is Manager -> handleAPK(it, subject)
|
||||
else -> it
|
||||
}
|
||||
}
|
||||
|
||||
// ---
|
||||
|
@ -17,7 +17,6 @@ data class UninstallerJson(
|
||||
val link: String = ""
|
||||
)
|
||||
|
||||
@Parcelize
|
||||
@JsonSerializable
|
||||
data class MagiskJson(
|
||||
val version: String = "",
|
||||
@ -25,12 +24,13 @@ data class MagiskJson(
|
||||
val link: String = "",
|
||||
val note: String = "",
|
||||
@Json(name = "md5") val hash: String = ""
|
||||
) : Parcelable
|
||||
)
|
||||
|
||||
@Parcelize
|
||||
@JsonSerializable
|
||||
data class ManagerJson(
|
||||
val version: String = "",
|
||||
val versionCode: Int = -1,
|
||||
val link: String = "",
|
||||
val note: String = ""
|
||||
)
|
||||
) : Parcelable
|
||||
|
@ -16,6 +16,15 @@ sealed class Configuration : Parcelable {
|
||||
|
||||
}
|
||||
|
||||
sealed class APK : Configuration() {
|
||||
|
||||
@Parcelize
|
||||
object Upgrade : APK()
|
||||
|
||||
@Parcelize
|
||||
object Restore : APK()
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
object Download : Configuration()
|
||||
|
||||
|
@ -7,6 +7,7 @@ import com.topjohnwu.magisk.Info
|
||||
import com.topjohnwu.magisk.extensions.cachedFile
|
||||
import com.topjohnwu.magisk.extensions.get
|
||||
import com.topjohnwu.magisk.model.entity.MagiskJson
|
||||
import com.topjohnwu.magisk.model.entity.ManagerJson
|
||||
import com.topjohnwu.magisk.model.entity.module.Repo
|
||||
import kotlinx.android.parcel.IgnoredOnParcel
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
@ -20,8 +21,8 @@ sealed class DownloadSubject : Parcelable {
|
||||
|
||||
@Parcelize
|
||||
data class Module(
|
||||
val module: Repo,
|
||||
val configuration: Configuration
|
||||
val module: Repo,
|
||||
val configuration: Configuration
|
||||
) : DownloadSubject() {
|
||||
override val url: String get() = module.zipUrl
|
||||
|
||||
@ -31,6 +32,27 @@ sealed class DownloadSubject : Parcelable {
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class Manager(
|
||||
val configuration: Configuration.APK
|
||||
) : DownloadSubject() {
|
||||
|
||||
@IgnoredOnParcel
|
||||
val manager: ManagerJson = Info.remote.app
|
||||
|
||||
override val title: String
|
||||
get() = "MagiskManager-v${manager.version}(${manager.versionCode})"
|
||||
|
||||
override val url: String
|
||||
get() = manager.link
|
||||
|
||||
@IgnoredOnParcel
|
||||
override val file by lazy {
|
||||
get<Context>().cachedFile("manager.apk")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sealed class Magisk : DownloadSubject() {
|
||||
|
||||
abstract val configuration: Configuration
|
||||
|
@ -11,8 +11,11 @@ import com.topjohnwu.magisk.data.database.PolicyDao
|
||||
import com.topjohnwu.magisk.data.database.base.su
|
||||
import com.topjohnwu.magisk.extensions.inject
|
||||
import com.topjohnwu.magisk.extensions.reboot
|
||||
import com.topjohnwu.magisk.model.download.DownloadService
|
||||
import com.topjohnwu.magisk.model.entity.ManagerJson
|
||||
import com.topjohnwu.magisk.model.entity.internal.Configuration
|
||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
|
||||
import com.topjohnwu.magisk.utils.DownloadApp
|
||||
import com.topjohnwu.magisk.utils.SuLogger
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import com.topjohnwu.magisk.view.Shortcuts
|
||||
@ -73,9 +76,12 @@ open class GeneralReceiver : BroadcastReceiver() {
|
||||
}
|
||||
Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setup(context)
|
||||
Const.Key.BROADCAST_MANAGER_UPDATE -> {
|
||||
Info.remote = Info.remote.copy(app = Info.remote.app.copy(
|
||||
link = intent.getStringExtra(Const.Key.INTENT_SET_LINK) ?: ""))
|
||||
DownloadApp.upgrade(intent.getStringExtra(Const.Key.INTENT_SET_NAME))
|
||||
intent.getParcelableExtra<ManagerJson>(Const.Key.INTENT_SET_APP)?.let {
|
||||
Info.remote = Info.remote.copy(app = it)
|
||||
}
|
||||
DownloadService(context) {
|
||||
subject = DownloadSubject.Manager(Configuration.APK.Upgrade)
|
||||
}
|
||||
}
|
||||
Const.Key.BROADCAST_REBOOT -> reboot()
|
||||
}
|
||||
|
@ -21,9 +21,15 @@ import com.topjohnwu.magisk.Const
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.data.database.RepoDao
|
||||
import com.topjohnwu.magisk.databinding.CustomDownloadDialogBinding
|
||||
import com.topjohnwu.magisk.model.download.DownloadService
|
||||
import com.topjohnwu.magisk.model.entity.internal.Configuration
|
||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||
import com.topjohnwu.magisk.model.observer.Observer
|
||||
import com.topjohnwu.magisk.ui.base.BasePreferenceFragment
|
||||
import com.topjohnwu.magisk.utils.*
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper
|
||||
import com.topjohnwu.magisk.utils.LocaleManager
|
||||
import com.topjohnwu.magisk.utils.PatchAPK
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog
|
||||
import com.topjohnwu.net.Networking
|
||||
import com.topjohnwu.superuser.Shell
|
||||
@ -71,7 +77,9 @@ class SettingsFragment : BasePreferenceFragment() {
|
||||
}
|
||||
val restoreManager = findPreference("restore")
|
||||
restoreManager.setOnPreferenceClickListener {
|
||||
DownloadApp.restore()
|
||||
DownloadService(requireContext()) {
|
||||
subject = DownloadSubject.Manager(Configuration.APK.Restore)
|
||||
}
|
||||
true
|
||||
}
|
||||
findPreference("clear").setOnPreferenceClickListener {
|
||||
|
@ -1,99 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.ComponentName;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.ClassMap;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Info;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.ui.SplashActivity;
|
||||
import com.topjohnwu.magisk.view.ProgressNotification;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.net.ResponseListener;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import dalvik.system.DexClassLoader;
|
||||
|
||||
public class DownloadApp {
|
||||
|
||||
public static void upgrade(String name) {
|
||||
dlInstall(name, new PatchPackageName());
|
||||
}
|
||||
|
||||
public static void restore() {
|
||||
String name = Utils.INSTANCE.fmt("MagiskManager v%s(%d)",
|
||||
Info.remote.getApp().getVersion(), Info.remote.getApp().getVersionCode());
|
||||
dlInstall(name, new RestoreManager());
|
||||
}
|
||||
|
||||
private static void dlInstall(String name, ManagerDownloadListener listener) {
|
||||
File apk = new File(App.self.getCacheDir(), "manager.apk");
|
||||
ProgressNotification progress = new ProgressNotification(name);
|
||||
listener.progress = progress;
|
||||
Networking.get(Info.remote.getApp().getLink())
|
||||
.setExecutor(App.THREAD_POOL)
|
||||
.setDownloadProgressListener(progress)
|
||||
.setErrorHandler((conn, e) -> progress.dlFail())
|
||||
.getAsFile(apk, listener);
|
||||
}
|
||||
|
||||
private abstract static class ManagerDownloadListener implements ResponseListener<File> {
|
||||
ProgressNotification progress;
|
||||
}
|
||||
|
||||
private static class PatchPackageName extends ManagerDownloadListener {
|
||||
|
||||
@Override
|
||||
public void onResponse(File apk) {
|
||||
File patched = apk;
|
||||
App app = App.self;
|
||||
if (!app.getPackageName().equals(BuildConfig.APPLICATION_ID)) {
|
||||
progress.getNotificationBuilder()
|
||||
.setProgress(0, 0, true)
|
||||
.setContentTitle(app.getString(R.string.hide_manager_title))
|
||||
.setContentText("");
|
||||
progress.update();
|
||||
patched = new File(apk.getParent(), "patched.apk");
|
||||
try {
|
||||
// Try using the new APK to patch itself
|
||||
ClassLoader loader = new DexClassLoader(apk.getPath(),
|
||||
apk.getParent(), null, ClassLoader.getSystemClassLoader());
|
||||
loader.loadClass("a.a")
|
||||
.getMethod("patchAPK", String.class, String.class, String.class)
|
||||
.invoke(null, apk.getPath(), patched.getPath(), app.getPackageName());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
// Fallback to use the current implementation
|
||||
PatchAPK.patch(apk.getPath(), patched.getPath(), app.getPackageName());
|
||||
}
|
||||
}
|
||||
APKInstall.install(app, patched);
|
||||
progress.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
private static class RestoreManager extends ManagerDownloadListener {
|
||||
|
||||
@Override
|
||||
public void onResponse(File apk) {
|
||||
App app = App.self;
|
||||
progress.getNotificationBuilder()
|
||||
.setProgress(0, 0, true)
|
||||
.setContentTitle(app.getString(R.string.restore_img_msg))
|
||||
.setContentText("");
|
||||
progress.update();
|
||||
Config.export();
|
||||
// Make it world readable
|
||||
apk.setReadable(true, false);
|
||||
if (Shell.su("pm install " + apk).exec().isSuccess())
|
||||
RootUtils.rmAndLaunch(app.getPackageName(),
|
||||
new ComponentName(BuildConfig.APPLICATION_ID,
|
||||
ClassMap.get(SplashActivity.class).getName()));
|
||||
progress.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
@ -18,7 +18,6 @@ import com.topjohnwu.magisk.Info;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.model.receiver.GeneralReceiver;
|
||||
import com.topjohnwu.magisk.ui.SplashActivity;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
public class Notifications {
|
||||
|
||||
@ -61,13 +60,11 @@ public class Notifications {
|
||||
|
||||
public static void managerUpdate() {
|
||||
App app = App.self;
|
||||
String name = Utils.INSTANCE.fmt("MagiskManager v%s(%d)",
|
||||
Info.remote.getApp().getVersion(), Info.remote.getApp().getVersionCode());
|
||||
|
||||
Intent intent = new Intent(app, ClassMap.get(GeneralReceiver.class));
|
||||
intent.setAction(Const.Key.BROADCAST_MANAGER_UPDATE);
|
||||
intent.putExtra(Const.Key.INTENT_SET_LINK, Info.remote.getApp().getLink());
|
||||
intent.putExtra(Const.Key.INTENT_SET_NAME, name);
|
||||
intent.putExtra(Const.Key.INTENT_SET_APP, Info.remote.getApp());
|
||||
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(app,
|
||||
Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
|
@ -3,18 +3,21 @@ package com.topjohnwu.magisk.view.dialogs
|
||||
import android.app.Activity
|
||||
import com.topjohnwu.magisk.Info
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.utils.DownloadApp
|
||||
import com.topjohnwu.magisk.model.download.DownloadService
|
||||
import com.topjohnwu.magisk.model.entity.internal.Configuration
|
||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||
import com.topjohnwu.magisk.view.MarkDownWindow
|
||||
|
||||
class ManagerInstallDialog(a: Activity) : CustomAlertDialog(a) {
|
||||
|
||||
init {
|
||||
val name = "MagiskManager v${Info.remote.app.version}" +
|
||||
"(${Info.remote.app.versionCode})"
|
||||
val subject = DownloadSubject.Manager(Configuration.APK.Upgrade)
|
||||
setTitle(a.getString(R.string.repo_install_title, a.getString(R.string.app_name)))
|
||||
setMessage(a.getString(R.string.repo_install_msg, name))
|
||||
setMessage(a.getString(R.string.repo_install_msg, subject.title))
|
||||
setCancelable(true)
|
||||
setPositiveButton(R.string.install) { _, _ -> DownloadApp.upgrade(name) }
|
||||
setPositiveButton(R.string.install) { _, _ ->
|
||||
DownloadService(a) { this.subject = subject }
|
||||
}
|
||||
if (Info.remote.app.note.isNotEmpty()) {
|
||||
setNeutralButton(R.string.app_changelog) { _, _ ->
|
||||
MarkDownWindow.show(a, null, Info.remote.app.note) }
|
||||
|
@ -9,6 +9,10 @@ import java.io.File;
|
||||
|
||||
public class APKInstall {
|
||||
public static void install(Context c, File apk) {
|
||||
c.startActivity(installIntent(c, apk));
|
||||
}
|
||||
|
||||
public static Intent installIntent(Context c, File apk) {
|
||||
Intent install = new Intent(Intent.ACTION_INSTALL_PACKAGE);
|
||||
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
@ -18,6 +22,6 @@ public class APKInstall {
|
||||
apk.setReadable(true, false);
|
||||
install.setData(Uri.fromFile(apk));
|
||||
}
|
||||
c.startActivity(install);
|
||||
return install;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user