Download Magisk Manager via new service

This commit is contained in:
topjohnwu 2019-07-29 00:26:18 -07:00
parent 3d81f167ea
commit 85f5ff3c14
14 changed files with 170 additions and 128 deletions

View File

@ -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"

View File

@ -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)

View File

@ -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())
}

View File

@ -27,7 +27,7 @@ abstract class NotificationService : Service() {
// --
protected fun update(
fun update(
id: Int,
body: (NotificationCompat.Builder) -> Unit = {}
) {

View File

@ -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
}
}
// ---

View File

@ -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

View File

@ -16,6 +16,15 @@ sealed class Configuration : Parcelable {
}
sealed class APK : Configuration() {
@Parcelize
object Upgrade : APK()
@Parcelize
object Restore : APK()
}
@Parcelize
object Download : Configuration()

View File

@ -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

View File

@ -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()
}

View File

@ -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 {

View File

@ -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();
}
}
}

View File

@ -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);

View File

@ -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) }

View File

@ -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;
}
}