Separate core components

This commit is contained in:
topjohnwu
2020-01-13 22:01:46 +08:00
parent ba1a2fbce4
commit 707d7b3342
134 changed files with 742 additions and 693 deletions
@@ -1,156 +0,0 @@
package com.topjohnwu.magisk.model.download
import android.annotation.SuppressLint
import android.app.Notification
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.webkit.MimeTypeMap
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.extensions.chooser
import com.topjohnwu.magisk.extensions.exists
import com.topjohnwu.magisk.extensions.provide
import com.topjohnwu.magisk.intent
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.*
import com.topjohnwu.magisk.legacy.flash.FlashActivity
import com.topjohnwu.magisk.utils.APKInstall
import org.koin.core.get
import java.io.File
import kotlin.random.Random.Default.nextInt
/* More of a facade for [RemoteFileService], but whatever... */
@SuppressLint("Registered")
open class DownloadService : RemoteFileService() {
private val context get() = this
private val File.type
get() = MimeTypeMap.getSingleton()
.getMimeTypeFromExtension(extension)
?: "resource/folder"
override fun onFinished(subject: DownloadSubject, id: Int) = when (subject) {
is Magisk -> onFinishedInternal(subject, id)
is Module -> onFinishedInternal(subject, id)
is Manager -> onFinishedInternal(subject, id)
}
private fun onFinishedInternal(
subject: Magisk,
id: Int
) = when (val conf = subject.configuration) {
Uninstall -> FlashActivity.uninstall(this, subject.file, id)
is Patch -> FlashActivity.patch(this, subject.file, conf.fileUri, id)
is Flash -> FlashActivity.flash(this, subject.file, conf is Secondary, id)
else -> Unit
}
private fun onFinishedInternal(
subject: Module,
id: Int
) = when (subject.configuration) {
is Flash -> FlashActivity.install(this, subject.file, id)
else -> Unit
}
private fun onFinishedInternal(
subject: Manager,
id: Int
) {
remove(id)
when (subject.configuration) {
is APK.Upgrade -> APKInstall.install(this, subject.file)
is APK.Restore -> Unit
}
}
// ---
override fun Notification.Builder.addActions(subject: DownloadSubject)
= when (subject) {
is Magisk -> addActionsInternal(subject)
is Module -> addActionsInternal(subject)
is Manager -> addActionsInternal(subject)
}
private fun Notification.Builder.addActionsInternal(subject: Magisk)
= when (val conf = subject.configuration) {
Download -> this.apply {
fileIntent(subject.file.parentFile!!)
.takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_parent, it.chooser()) }
fileIntent(subject.file)
.takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_self, it.chooser()) }
}
Uninstall -> setContentIntent(FlashActivity.uninstallIntent(context, subject.file))
is Flash -> setContentIntent(FlashActivity.flashIntent(context, subject.file, conf is Secondary))
is Patch -> setContentIntent(FlashActivity.patchIntent(context, subject.file, conf.fileUri))
else -> this
}
private fun Notification.Builder.addActionsInternal(subject: Module)
= when (subject.configuration) {
Download -> this.apply {
fileIntent(subject.file.parentFile!!)
.takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_parent, it.chooser()) }
fileIntent(subject.file)
.takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_self, it.chooser()) }
}
is Flash -> setContentIntent(FlashActivity.installIntent(context, subject.file))
else -> this
}
private fun Notification.Builder.addActionsInternal(subject: Manager)
= when (subject.configuration) {
APK.Upgrade -> setContentIntent(APKInstall.installIntent(context, subject.file))
else -> this
}
@Suppress("ReplaceSingleLineLet")
private fun Notification.Builder.setContentIntent(intent: Intent) =
setContentIntent(
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
)
@Suppress("ReplaceSingleLineLet")
private fun Notification.Builder.addAction(icon: Int, title: Int, intent: Intent) =
addAction(icon, getString(title),
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
)
// ---
private fun fileIntent(file: File): Intent {
return Intent(Intent.ACTION_VIEW)
.setDataAndType(file.provide(this), file.type)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
class Builder {
lateinit var subject: DownloadSubject
}
companion object {
inline operator fun invoke(context: Context, argBuilder: Builder.() -> Unit) {
val app = context.applicationContext
val builder = Builder().apply(argBuilder)
val intent = app.intent<DownloadService>().putExtra(ARG_URL, builder.subject)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
app.startForegroundService(intent)
} else {
app.startService(intent)
}
}
}
}
@@ -1,65 +0,0 @@
package com.topjohnwu.magisk.model.download
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.extensions.writeTo
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.utils.PatchAPK
import com.topjohnwu.superuser.Shell
import java.io.File
private fun RemoteFileService.patch(apk: File, id: Int) {
if (packageName == BuildConfig.APPLICATION_ID)
return
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")
PatchAPK.patch(apk, patched, packageName, applicationInfo.nonLocalizedLabel.toString())
apk.delete()
patched.renameTo(apk)
}
private fun RemoteFileService.upgrade(apk: File, id: Int) {
if (isRunningAsStub) {
// Move to upgrade location
apk.copyTo(DynAPK.update(this), overwrite = true)
apk.delete()
if (Info.stub!!.version < Info.remote.stub.versionCode) {
// We also want to upgrade stub
service.fetchFile(Info.remote.stub.link).blockingGet().byteStream().use {
it.writeTo(apk)
}
patch(apk, id)
} else {
// Simply relaunch the app
ProcessPhoenix.triggerRebirth(this, intent<ProcessPhoenix>())
}
} else {
patch(apk, id)
}
}
private fun RemoteFileService.restore(apk: File, id: Int) {
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)
Shell.su("pm install $apk && pm uninstall $packageName").exec()
}
fun RemoteFileService.handleAPK(subject: DownloadSubject.Manager)
= when (subject.configuration) {
is Upgrade -> upgrade(subject.file, subject.hashCode())
is Restore -> restore(subject.file, subject.hashCode())
}
@@ -1,44 +0,0 @@
package com.topjohnwu.magisk.model.download
import com.topjohnwu.magisk.extensions.withStreams
import java.io.File
import java.io.InputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
fun InputStream.toModule(file: File, installer: InputStream) {
val input = ZipInputStream(buffered())
val output = ZipOutputStream(file.outputStream().buffered())
withStreams(input, output) { zin, zout ->
zout.putNextEntry(ZipEntry("META-INF/"))
zout.putNextEntry(ZipEntry("META-INF/com/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/android/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/android/update-binary"))
installer.copyTo(zout)
zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script"))
zout.write("#MAGISK\n".toByteArray(charset("UTF-8")))
var off = -1
var entry: ZipEntry? = zin.nextEntry
while (entry != null) {
if (off < 0) {
off = entry.name.indexOf('/') + 1
}
val path = entry.name.substring(off)
if (path.isNotEmpty() && !path.startsWith("META-INF")) {
zout.putNextEntry(ZipEntry(path))
if (!entry.isDirectory) {
zin.copyTo(zout)
}
}
entry = zin.nextEntry
}
}
}
@@ -1,85 +0,0 @@
package com.topjohnwu.magisk.model.download
import android.app.Notification
import android.content.Intent
import android.os.IBinder
import com.topjohnwu.magisk.base.BaseService
import com.topjohnwu.magisk.view.Notifications
import org.koin.core.KoinComponent
import java.util.*
import kotlin.random.Random.Default.nextInt
abstract class NotificationService : BaseService(), KoinComponent {
abstract val defaultNotification: Notification.Builder
private val hasNotifications get() = notifications.isNotEmpty()
private val notifications =
Collections.synchronizedMap(mutableMapOf<Int, Notification.Builder>())
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
notifications.forEach { cancel(it.key) }
notifications.clear()
}
// --
fun update(
id: Int,
body: (Notification.Builder) -> Unit = {}
) {
val notification = notifications.getOrPut(id) { defaultNotification }
notify(id, notification.also(body).build())
if (notifications.size == 1) {
updateForeground()
}
}
protected fun finishNotify(
id: Int,
editBody: (Notification.Builder) -> Notification.Builder? = { null }
) : Int {
val currentNotification = remove(id)?.run(editBody)
var newId = -1
currentNotification?.let {
newId = nextInt(Int.MAX_VALUE)
notify(newId, it.build())
}
if (!hasNotifications) {
stopSelf()
}
return newId
}
// ---
private fun notify(id: Int, notification: Notification) {
Notifications.mgr.notify(id, notification)
}
private fun cancel(id: Int) {
Notifications.mgr.cancel(id)
}
protected fun remove(id: Int) = notifications.remove(id).also {
cancel(id)
updateForeground()
}
private fun updateForeground() {
if (hasNotifications)
startForeground(notifications.keys.first(), notifications.values.first().build())
else
stopForeground(true)
}
// --
override fun onBind(p0: Intent?): IBinder? = null
}
@@ -1,138 +0,0 @@
package com.topjohnwu.magisk.model.download
import android.app.Activity
import android.app.Notification
import android.content.Intent
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.data.network.GithubRawServices
import com.topjohnwu.magisk.di.NullActivity
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.extensions.writeTo
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
import com.topjohnwu.magisk.utils.ProgressInputStream
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.superuser.ShellUtils
import io.reactivex.Completable
import okhttp3.ResponseBody
import org.koin.android.ext.android.inject
import org.koin.core.KoinComponent
import timber.log.Timber
import java.io.InputStream
abstract class RemoteFileService : NotificationService() {
val service: GithubRawServices by inject()
override val defaultNotification
get() = Notifications.progress(this, "")
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
intent?.getParcelableExtra<DownloadSubject>(ARG_URL)?.let { start(it) }
return START_REDELIVER_INTENT
}
// ---
private fun start(subject: DownloadSubject) = checkExisting(subject)
.onErrorResumeNext { download(subject) }
.doOnSubscribe { update(subject.hashCode()) { it.setContentTitle(subject.title) } }
.subscribeK(onError = {
Timber.e(it)
failNotify(subject)
}) {
val newId = finishNotify(subject)
if (get<Activity>() !is NullActivity) {
onFinished(subject, newId)
}
}
private fun checkExisting(subject: DownloadSubject) = Completable.fromAction {
check(subject is Magisk) { "Download cache is disabled" }
subject.file.also {
check(it.exists() && ShellUtils.checkSum("MD5", it, subject.magisk.md5)) {
"The given file does not match checksum"
}
}
}
private fun download(subject: DownloadSubject) = service.fetchFile(subject.url)
.map { it.toStream(subject.hashCode(), subject) }
.flatMapCompletable { stream ->
when (subject) {
is Module -> service.fetchInstaller()
.doOnSuccess { stream.toModule(subject.file, it.byteStream()) }
.ignoreElement()
else -> Completable.fromAction { stream.writeTo(subject.file) }
}
}.doOnComplete {
if (subject is Manager)
handleAPK(subject)
}
private fun ResponseBody.toStream(id: Int, subject: DownloadSubject): InputStream {
val maxRaw = contentLength()
val max = maxRaw / 1_000_000f
return ProgressInputStream(byteStream()) {
val progress = it / 1_000_000f
update(id) { notification ->
if (maxRaw > 0) {
send(progress / max, subject)
notification
.setProgress(maxRaw.toInt(), it.toInt(), false)
.setContentText("%.2f / %.2f MB".format(progress, max))
} else {
send(-1f, subject)
notification.setContentText("%.2f MB / ??".format(progress))
}
}
}
}
private fun failNotify(subject: DownloadSubject) = finishNotify(subject.hashCode()) {
send(0f, subject)
it.setContentText(getString(R.string.download_file_error))
.setSmallIcon(android.R.drawable.stat_notify_error)
.setOngoing(false)
}
private fun finishNotify(subject: DownloadSubject) = finishNotify(subject.hashCode()) {
send(1f, subject)
it.addActions(subject)
.setContentText(getString(R.string.download_complete))
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setProgress(0, 0, false)
.setOngoing(false)
.setAutoCancel(true)
}
// ---
@Throws(Throwable::class)
protected abstract fun onFinished(subject: DownloadSubject, id: Int)
protected abstract fun Notification.Builder.addActions(subject: DownloadSubject)
: Notification.Builder
companion object : KoinComponent {
const val ARG_URL = "arg_url"
private val internalProgressBroadcast = MutableLiveData<Pair<Float, DownloadSubject>>()
val progressBroadcast: LiveData<Pair<Float, DownloadSubject>> get() = internalProgressBroadcast
fun send(progress: Float, subject: DownloadSubject) {
internalProgressBroadcast.postValue(progress to subject)
}
fun reset() {
internalProgressBroadcast.value = null
}
}
}
@@ -3,10 +3,11 @@ package com.topjohnwu.magisk.model.entity
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import com.topjohnwu.magisk.core.model.MagiskPolicy
import com.topjohnwu.magisk.core.model.MagiskPolicy.Companion.ALLOW
import com.topjohnwu.magisk.extensions.now
import com.topjohnwu.magisk.extensions.timeFormatTime
import com.topjohnwu.magisk.extensions.toTime
import com.topjohnwu.magisk.model.entity.MagiskPolicy.Companion.ALLOW
@Entity(tableName = "logs")
data class MagiskLog(
@@ -23,11 +24,6 @@ data class MagiskLog(
@Ignore val timeString = time.toTime(timeFormatTime)
}
data class WrappedMagiskLog(
val time: Long,
val items: List<MagiskLog>
)
fun MagiskPolicy.toLog(
toUid: Int,
fromPid: Int,
@@ -1,70 +0,0 @@
package com.topjohnwu.magisk.model.entity
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import com.topjohnwu.magisk.extensions.getLabel
import com.topjohnwu.magisk.model.entity.MagiskPolicy.Companion.INTERACTIVE
data class MagiskPolicy(
var uid: Int,
val packageName: String,
val appName: String,
var policy: Int = INTERACTIVE,
var until: Long = -1L,
val logging: Boolean = true,
val notification: Boolean = true,
val applicationInfo: ApplicationInfo
) {
companion object {
const val INTERACTIVE = 0
const val DENY = 1
const val ALLOW = 2
}
}
fun MagiskPolicy.toMap() = mapOf(
"uid" to uid,
"package_name" to packageName,
"policy" to policy,
"until" to until,
"logging" to logging,
"notification" to notification
)
@Throws(PackageManager.NameNotFoundException::class)
fun Map<String, String>.toPolicy(pm: PackageManager): MagiskPolicy {
val uid = get("uid")?.toIntOrNull() ?: -1
val packageName = get("package_name").orEmpty()
val info = pm.getApplicationInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES)
if (info.uid != uid)
throw PackageManager.NameNotFoundException()
return MagiskPolicy(
uid = uid,
packageName = packageName,
policy = get("policy")?.toIntOrNull() ?: INTERACTIVE,
until = get("until")?.toLongOrNull() ?: -1L,
logging = get("logging")?.toIntOrNull() != 0,
notification = get("notification")?.toIntOrNull() != 0,
applicationInfo = info,
appName = info.getLabel(pm)
)
}
@Throws(PackageManager.NameNotFoundException::class)
fun Int.toPolicy(pm: PackageManager, policy: Int = INTERACTIVE): MagiskPolicy {
val pkg = pm.getPackagesForUid(this)?.firstOrNull()
?: throw PackageManager.NameNotFoundException()
val info = pm.getApplicationInfo(pkg, PackageManager.GET_UNINSTALLED_PACKAGES)
return MagiskPolicy(
uid = info.uid,
packageName = pkg,
policy = policy,
applicationInfo = info,
appName = info.getLabel(pm)
)
}
@@ -1,42 +0,0 @@
package com.topjohnwu.magisk.model.entity
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
import se.ansman.kotshi.JsonSerializable
@JsonSerializable
data class UpdateInfo(
val app: ManagerJson = ManagerJson(),
val uninstaller: UninstallerJson = UninstallerJson(),
val magisk: MagiskJson = MagiskJson(),
val stub: StubJson = StubJson()
)
@JsonSerializable
data class UninstallerJson(
val link: String = ""
)
@JsonSerializable
data class MagiskJson(
val version: String = "",
val versionCode: Int = -1,
val link: String = "",
val note: String = "",
val md5: String = ""
)
@Parcelize
@JsonSerializable
data class ManagerJson(
val version: String = "",
val versionCode: Int = -1,
val link: String = "",
val note: String = ""
) : Parcelable
@JsonSerializable
data class StubJson(
val versionCode: Int = -1,
val link: String = ""
)
@@ -2,13 +2,13 @@ package com.topjohnwu.magisk.model.entity.internal
import android.content.Context
import android.os.Parcelable
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.model.module.Repo
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 com.topjohnwu.magisk.core.model.MagiskJson
import com.topjohnwu.magisk.core.model.ManagerJson
import kotlinx.android.parcel.IgnoredOnParcel
import kotlinx.android.parcel.Parcelize
import java.io.File
@@ -103,4 +103,4 @@ sealed class DownloadSubject : Parcelable {
}
}
}
@@ -1,41 +0,0 @@
package com.topjohnwu.magisk.model.entity.module
abstract class BaseModule : Comparable<BaseModule> {
abstract var id: String
protected set
abstract var name: String
protected set
abstract var author: String
protected set
abstract var version: String
protected set
abstract var versionCode: Int
protected set
abstract var description: String
protected set
@Throws(NumberFormatException::class)
protected fun parseProps(props: List<String>) {
for (line in props) {
val prop = line.split("=".toRegex(), 2).map { it.trim() }
if (prop.size != 2)
continue
val key = prop[0]
val value = prop[1]
if (key.isEmpty() || key[0] == '#')
continue
when (key) {
"id" -> id = value
"name" -> name = value
"version" -> version = value
"versionCode" -> versionCode = value.toInt()
"author" -> author = value
"description" -> description = value
}
}
}
override operator fun compareTo(other: BaseModule) = name.compareTo(other.name, true)
}
@@ -1,79 +0,0 @@
package com.topjohnwu.magisk.model.entity.module
import androidx.annotation.WorkerThread
import com.topjohnwu.magisk.Const
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.io.SuFile
class Module(path: String) : BaseModule() {
override var id: String = ""
override var name: String = ""
override var author: String = ""
override var version: String = ""
override var versionCode: Int = -1
override var description: String = ""
private val removeFile = SuFile(path, "remove")
private val disableFile = SuFile(path, "disable")
private val updateFile = SuFile(path, "update")
private val ruleFile = SuFile(path, "sepolicy.rule")
val updated: Boolean = updateFile.exists()
var enable: Boolean = !disableFile.exists()
set(enable) {
val dir = "$PERSIST/$id"
field = if (enable) {
Shell.su("mkdir -p $dir", "cp -af $ruleFile $dir").submit()
disableFile.delete()
} else {
Shell.su("rm -rf $dir").submit()
!disableFile.createNewFile()
}
}
var remove: Boolean = removeFile.exists()
set(remove) {
field = if (remove) {
Shell.su("rm -rf $PERSIST/$id").submit()
removeFile.createNewFile()
} else {
Shell.su("cp -af $ruleFile $PERSIST/$id").submit()
!removeFile.delete()
}
}
init {
runCatching {
parseProps(Shell.su("dos2unix < $path/module.prop").exec().out)
}
if (id.isEmpty()) {
val sep = path.lastIndexOf('/')
id = path.substring(sep + 1)
}
if (name.isEmpty()) {
name = id
}
}
companion object {
private const val PERSIST = "/sbin/.magisk/mirror/persist/magisk"
@WorkerThread
fun loadModules(): List<Module> {
val moduleList = mutableListOf<Module>()
val path = SuFile(Const.MAGISK_PATH)
val modules =
path.listFiles { _, name -> name != "lost+found" && name != ".core" }.orEmpty()
for (file in modules) {
if (file.isFile) continue
val module = Module(Const.MAGISK_PATH + "/" + file.name)
moduleList.add(module)
}
return moduleList.sortedBy { it.name.toLowerCase() }
}
}
}
@@ -1,71 +0,0 @@
package com.topjohnwu.magisk.model.entity.module
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.data.repository.StringRepository
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.legalFilename
import kotlinx.android.parcel.Parcelize
import java.text.DateFormat
import java.util.*
@Entity(tableName = "repos")
@Parcelize
data class Repo(
@PrimaryKey override var id: String,
override var name: String,
override var author: String,
override var version: String,
override var versionCode: Int,
override var description: String,
var last_update: Long
) : BaseModule(), Parcelable {
private val stringRepo: StringRepository get() = get()
val lastUpdate get() = Date(last_update)
val lastUpdateString: String get() = dateFormat.format(lastUpdate)
val downloadFilename: String get() = "$name-$version($versionCode).zip".legalFilename()
val readme get() = stringRepo.getReadme(this)
val zipUrl: String get() = Const.Url.ZIP_URL.format(id)
constructor(id: String) : this(id, "", "", "", -1, "", 0)
@Throws(IllegalRepoException::class)
fun update() {
val props = runCatching {
stringRepo.getMetadata(this).blockingGet()
.orEmpty().split("\\n".toRegex()).dropLastWhile { it.isEmpty() }
}.getOrElse {
throw IllegalRepoException("Repo [$id] module.prop download error: " + it.message)
}
props.runCatching {
parseProps(this)
}.onFailure {
throw IllegalRepoException("Repo [$id] parse error: " + it.message)
}
if (versionCode < 0) {
throw IllegalRepoException("Repo [$id] does not contain versionCode")
}
}
@Throws(IllegalRepoException::class)
fun update(lastUpdate: Date) {
last_update = lastUpdate.time
update()
}
class IllegalRepoException(message: String) : Exception(message)
companion object {
val dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM)!!
}
}
@@ -1,7 +1,7 @@
package com.topjohnwu.magisk.model.entity.recycler
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.databinding.ComparableRvItem
sealed class HomeItem : ComparableRvItem<HomeItem>() {
@@ -7,9 +7,9 @@ import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.model.module.Module
import com.topjohnwu.magisk.core.model.module.Repo
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.model.entity.module.Module
import com.topjohnwu.magisk.model.entity.module.Repo
import com.topjohnwu.magisk.ui.module.ModuleViewModel
import com.topjohnwu.magisk.utils.KObservableField
@@ -2,9 +2,9 @@ package com.topjohnwu.magisk.model.entity.recycler
import android.graphics.drawable.Drawable
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.model.MagiskPolicy
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.toggle
import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
import com.topjohnwu.magisk.utils.KObservableField
@@ -2,7 +2,7 @@ package com.topjohnwu.magisk.model.events
import android.content.Context
import androidx.fragment.app.Fragment
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.core.base.BaseActivity
interface ContextExecutor {
@@ -12,7 +12,7 @@ interface ContextExecutor {
interface ActivityExecutor {
operator fun invoke(activity: BaseActivity<*, *>)
operator fun invoke(activity: BaseActivity)
}
@@ -3,14 +3,14 @@ package com.topjohnwu.magisk.model.events
import android.app.Activity
import android.content.Context
import android.content.Intent
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.intent
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.legacy.flash.FlashActivity
class InstallExternalModuleEvent : ViewEvent(), ActivityExecutor {
override fun invoke(activity: BaseActivity<*, *>) {
override fun invoke(activity: BaseActivity) {
activity.withExternalRW {
onSuccess {
val intent = Intent(Intent.ACTION_GET_CONTENT)
@@ -1,6 +1,6 @@
package com.topjohnwu.magisk.model.events
import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.core.model.MagiskPolicy
import com.topjohnwu.magisk.utils.RxBus
sealed class PolicyUpdateEvent(val item: MagiskPolicy) : RxBus.Event {
@@ -3,8 +3,9 @@ package com.topjohnwu.magisk.model.events
import android.content.Context
import androidx.annotation.StringRes
import com.google.android.material.snackbar.Snackbar
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.extensions.snackbar
import com.topjohnwu.magisk.ui.base.BaseUIActivity
class SnackbarEvent private constructor(
@StringRes private val messageRes: Int,
@@ -27,8 +28,8 @@ class SnackbarEvent private constructor(
fun message(context: Context): String = messageString ?: context.getString(messageRes)
override fun invoke(activity: BaseActivity<*, *>) {
if (activity is BaseActivity<*, *>) {
override fun invoke(activity: BaseActivity) {
if (activity is BaseUIActivity<*, *>) {
activity.snackbar(activity.snackbarView, message(activity), length, f)
}
}
@@ -3,16 +3,16 @@ package com.topjohnwu.magisk.model.events
import android.app.Activity
import android.content.Context
import android.content.Intent
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.model.module.Repo
import com.topjohnwu.magisk.data.repository.MagiskRepository
import com.topjohnwu.magisk.extensions.DynamicClassLoader
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.extensions.writeTo
import com.topjohnwu.magisk.model.entity.module.Repo
import com.topjohnwu.magisk.utils.RxBus
import com.topjohnwu.magisk.utils.SafetyNetHelper
import com.topjohnwu.magisk.core.utils.SafetyNetHelper
import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.magisk.view.MarkDownWindow
import com.topjohnwu.superuser.Shell
@@ -118,8 +118,8 @@ class UpdateSafetyNetEvent : ViewEvent(), ContextExecutor, KoinComponent, Safety
}
}
class ViewActionEvent(val action: BaseActivity<*, *>.() -> Unit) : ViewEvent(), ActivityExecutor {
override fun invoke(activity: BaseActivity<*, *>) = activity.run(action)
class ViewActionEvent(val action: BaseActivity.() -> Unit) : ViewEvent(), ActivityExecutor {
override fun invoke(activity: BaseActivity) = activity.run(action)
}
class OpenChangelogEvent(val item: Repo) : ViewEvent(), ContextExecutor {
@@ -133,7 +133,7 @@ class PermissionEvent(
val callback: PublishSubject<Boolean>
) : ViewEvent(), ActivityExecutor {
override fun invoke(activity: BaseActivity<*, *>) =
override fun invoke(activity: BaseActivity) =
activity.withPermissions(*permissions.toTypedArray()) {
onSuccess {
callback.onNext(true)
@@ -146,25 +146,25 @@ class PermissionEvent(
}
class BackPressEvent : ViewEvent(), ActivityExecutor {
override fun invoke(activity: BaseActivity<*, *>) {
override fun invoke(activity: BaseActivity) {
activity.onBackPressed()
}
}
class DieEvent : ViewEvent(), ActivityExecutor {
override fun invoke(activity: BaseActivity<*, *>) {
override fun invoke(activity: BaseActivity) {
activity.finish()
}
}
class RecreateEvent : ViewEvent(), ActivityExecutor {
override fun invoke(activity: BaseActivity<*, *>) {
override fun invoke(activity: BaseActivity) {
activity.recreate()
}
}
class RequestFileEvent : ViewEvent(), ActivityExecutor {
override fun invoke(activity: BaseActivity<*, *>) {
override fun invoke(activity: BaseActivity) {
Intent(Intent.ACTION_GET_CONTENT)
.setType("*/*")
.addCategory(Intent.CATEGORY_OPENABLE)
@@ -1,9 +1,9 @@
package com.topjohnwu.magisk.model.events.dialog
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.model.events.ActivityExecutor
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.utils.BiometricHelper
import com.topjohnwu.magisk.core.utils.BiometricHelper
class BiometricDialog(
builder: Builder.() -> Unit
@@ -16,7 +16,7 @@ class BiometricDialog(
builder(Builder())
}
override fun invoke(activity: BaseActivity<*, *>) {
override fun invoke(activity: BaseActivity) {
BiometricHelper.authenticate(
activity,
onError = listenerOnFailure,
@@ -2,9 +2,9 @@ package com.topjohnwu.magisk.model.events.dialog
import android.app.Activity
import androidx.appcompat.app.AppCompatDelegate
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.model.events.ActivityExecutor
import com.topjohnwu.magisk.view.MagiskDialog
import java.lang.ref.WeakReference
@@ -13,7 +13,7 @@ class DarkThemeDialog : DialogEvent(), ActivityExecutor {
private var activity: WeakReference<Activity>? = null
override fun invoke(activity: BaseActivity<*, *>) {
override fun invoke(activity: BaseActivity) {
this.activity = WeakReference(activity)
}
@@ -1,24 +1,14 @@
package com.topjohnwu.magisk.model.events.dialog
import android.content.Context
import android.content.DialogInterface
import android.widget.Toast
import androidx.core.net.toUri
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.extensions.cachedFile
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.magisk.extensions.reboot
import com.topjohnwu.magisk.net.Networking
import com.topjohnwu.magisk.tasks.MagiskInstaller
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.core.utils.Utils
import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.internal.UiThreadHandler
import com.topjohnwu.superuser.io.SuFile
import org.koin.core.KoinComponent
import org.koin.core.get
import java.io.File
class EnvFixDialog : DialogEvent() {
@@ -48,19 +38,7 @@ class EnvFixDialog : DialogEvent() {
private fun fixEnv(dialog: DialogInterface) {
object : MagiskInstaller(), KoinComponent {
override fun operations(): Boolean {
val context = get<Context>()
val zip: File = context.cachedFile("magisk.zip")
installDir = SuFile("/data/adb/magisk")
Shell.su("rm -rf /data/adb/magisk/*").exec()
if (!ShellUtils.checkSum("MD5", zip, Info.remote.magisk.md5))
Networking.get(Info.remote.magisk.link).execForFile(zip)
zipUri = zip.toUri()
return extractZip() && Shell.su("fix_env").exec().isSuccess
}
override fun operations() = fixEnv()
override fun onResult(success: Boolean) {
dialog.dismiss()
@@ -74,4 +52,4 @@ class EnvFixDialog : DialogEvent() {
}.exec()
}
}
}
@@ -4,16 +4,16 @@ import android.Manifest
import android.net.Uri
import android.view.LayoutInflater
import android.widget.Toast
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.databinding.IncludeInstallOptionsBinding
import com.topjohnwu.magisk.extensions.hasPermissions
import com.topjohnwu.magisk.extensions.res
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.events.OpenInappLinkEvent
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.core.utils.Utils
import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.magisk.view.MarkDownWindow
import com.topjohnwu.superuser.Shell
@@ -128,4 +128,4 @@ class MagiskInstallDialog : DialogEvent() {
.fastCmd("grep_prop ro.build.ab_update")
.let { it.isNotEmpty() && it.toBoolean() }
}
}
@@ -1,9 +1,9 @@
package com.topjohnwu.magisk.model.events.dialog
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.extensions.res
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.MagiskDialog
@@ -33,4 +33,4 @@ class ManagerInstallDialog : DialogEvent() {
}
}
}
}
@@ -1,10 +1,10 @@
package com.topjohnwu.magisk.model.events.dialog
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.model.download.DownloadService
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.core.model.module.Repo
import com.topjohnwu.magisk.model.entity.internal.Configuration
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.model.entity.module.Repo
import com.topjohnwu.magisk.view.MagiskDialog
class ModuleInstallDialog(private val item: Repo) : DialogEvent() {
@@ -34,4 +34,4 @@ class ModuleInstallDialog(private val item: Repo) : DialogEvent() {
}
}
}
}
@@ -1,12 +1,12 @@
package com.topjohnwu.magisk.model.events.dialog
import android.widget.Toast
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.model.download.DownloadService
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.model.entity.internal.Configuration
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.core.utils.Utils
import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.superuser.Shell
@@ -52,4 +52,4 @@ class UninstallDialog : DialogEvent() {
}
}
}
}
@@ -3,8 +3,8 @@ package com.topjohnwu.magisk.model.flash
import android.content.Context
import android.net.Uri
import androidx.core.os.postDelayed
import com.topjohnwu.magisk.core.tasks.FlashZip
import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.magisk.tasks.FlashZip
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.internal.UiThreadHandler
@@ -58,4 +58,4 @@ sealed class Flashing(
}
}
}
}
@@ -1,7 +1,7 @@
package com.topjohnwu.magisk.model.flash
import android.net.Uri
import com.topjohnwu.magisk.tasks.MagiskInstaller
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.superuser.Shell
sealed class Patching(
@@ -28,8 +28,7 @@ sealed class Patching(
logs: MutableList<String>,
resultListener: FlashResultListener
) : Patching(file, console, logs, resultListener) {
override fun operations() =
extractZip() && handleFile(uri) && patchBoot() && storeBoot()
override fun operations() = doPatchFile(uri)
}
class SecondSlot(
@@ -38,8 +37,7 @@ sealed class Patching(
logs: MutableList<String>,
resultListener: FlashResultListener
) : Patching(file, console, logs, resultListener) {
override fun operations() =
findSecondaryImage() && extractZip() && patchBoot() && flashBoot() && postOTA()
override fun operations() = secondSlot()
}
class Direct(
@@ -48,8 +46,7 @@ sealed class Patching(
logs: MutableList<String>,
resultListener: FlashResultListener
) : Patching(file, console, logs, resultListener) {
override fun operations() =
findImage() && extractZip() && patchBoot() && flashBoot()
override fun operations() = direct()
}
}
}
@@ -4,10 +4,10 @@ import android.os.Bundle
import androidx.annotation.AnimRes
import androidx.annotation.AnimatorRes
import androidx.fragment.app.Fragment
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.model.events.ActivityExecutor
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.ui.compat.CompatActivity
import com.topjohnwu.magisk.ui.base.CompatActivity
import kotlin.reflect.KClass
@DslMarker
@@ -23,7 +23,7 @@ class MagiskNavigationEvent(
operator fun invoke(builder: Builder.() -> Unit) = Builder().apply(builder).build()
}
override fun invoke(activity: BaseActivity<*, *>) {
override fun invoke(activity: BaseActivity) {
if (activity !is CompatActivity<*, *>) return
activity.navigation?.navigateTo(this)
}
@@ -3,8 +3,8 @@ package com.topjohnwu.magisk.model.navigation
import android.content.Context
import android.content.Intent
import android.os.Build
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.intent
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.ui.MainActivity
import com.topjohnwu.magisk.ui.hide.HideFragment
import com.topjohnwu.magisk.ui.home.HomeFragment
@@ -82,7 +82,9 @@ object Navigation {
fun start(launchIntent: Intent, context: Context) {
context.intent<MainActivity>()
.putExtra(Const.Key.OPEN_SECTION, launchIntent.getStringExtra(Const.Key.OPEN_SECTION))
.putExtra(
Const.Key.OPEN_SECTION, launchIntent.getStringExtra(
Const.Key.OPEN_SECTION))
.putExtra(
Const.Key.OPEN_SETTINGS,
launchIntent.action == ACTION_APPLICATION_PREFERENCES
@@ -1,57 +0,0 @@
package com.topjohnwu.magisk.model.receiver
import android.content.ContextWrapper
import android.content.Intent
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.base.BaseReceiver
import com.topjohnwu.magisk.data.database.PolicyDao
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.utils.SuHandler
import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.superuser.Shell
import org.koin.core.inject
open class GeneralReceiver : BaseReceiver() {
private val policyDB: PolicyDao by inject()
private fun getPkg(intent: Intent): String {
return intent.data?.encodedSchemeSpecificPart.orEmpty()
}
override fun onReceive(context: ContextWrapper, intent: Intent?) {
intent ?: return
when (intent.action ?: return) {
Intent.ACTION_REBOOT -> {
SuHandler(context, intent.getStringExtra("action"), intent.extras)
}
Intent.ACTION_PACKAGE_REPLACED -> {
// This will only work pre-O
if (Config.suReAuth)
policyDB.delete(getPkg(intent)).blockingGet()
}
Intent.ACTION_PACKAGE_FULLY_REMOVED -> {
val pkg = getPkg(intent)
policyDB.delete(pkg).blockingGet()
Shell.su("magiskhide --rm $pkg").submit()
}
Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setup(context)
Const.Key.BROADCAST_MANAGER_UPDATE -> {
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()
}
}
}
@@ -1,30 +0,0 @@
package com.topjohnwu.magisk.model.update
import androidx.work.ListenableWorker
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.base.DelegateWorker
import com.topjohnwu.magisk.data.repository.MagiskRepository
import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.superuser.Shell
class UpdateCheckService : DelegateWorker() {
private val magiskRepo: MagiskRepository by inject()
override fun doWork(): ListenableWorker.Result {
// Make sure shell initializer was ran
Shell.getShell()
return runCatching {
magiskRepo.fetchUpdate().blockingGet()
if (BuildConfig.VERSION_CODE < Info.remote.app.versionCode)
Notifications.managerUpdate(applicationContext)
else if (Info.env.magiskVersionCode < Info.remote.magisk.versionCode)
Notifications.magiskUpdate(applicationContext)
ListenableWorker.Result.success()
}.getOrElse {
ListenableWorker.Result.failure()
}
}
}
@@ -1,75 +0,0 @@
package com.topjohnwu.magisk.model.zip
import com.topjohnwu.magisk.extensions.forEach
import com.topjohnwu.magisk.extensions.withStreams
import com.topjohnwu.superuser.io.SuFile
import java.io.File
import java.util.zip.ZipInputStream
class Zip private constructor(private val values: Builder) {
companion object {
operator fun invoke(builder: Builder.() -> Unit): Zip {
return Zip(Builder().apply(builder))
}
}
class Builder {
lateinit var zip: File
lateinit var destination: File
var excludeDirs = true
}
data class Path(val path: String, val pullFromDir: Boolean = true)
fun unzip(vararg paths: Pair<String, Boolean>) =
unzip(*paths.map { Path(it.first, it.second) }.toTypedArray())
@Suppress("RedundantLambdaArrow")
fun unzip(vararg paths: Path) {
ensureRequiredParams()
values.zip.zipStream().use {
it.forEach { e ->
val currentPath = paths.firstOrNull { e.name.startsWith(it.path) }
val isDirectory = values.excludeDirs && e.isDirectory
if (currentPath == null || isDirectory) {
// Ignore directories, only create files
return@forEach
}
val name = if (currentPath.pullFromDir) {
e.name.substring(e.name.lastIndexOf('/') + 1)
} else {
e.name
}
val out = File(values.destination, name)
.ensureExists()
.outputStream()
//.suOutputStream()
withStreams(it, out) { reader, writer ->
reader.copyTo(writer)
}
}
}
}
private fun ensureRequiredParams() {
if (!values.zip.exists()) {
throw RuntimeException("Zip file does not exist")
}
}
private fun File.ensureExists() =
if ((!parentFile.exists() && !parentFile.mkdirs()) || parentFile is SuFile) {
SuFile(parentFile, name).apply { parentFile.mkdirs() }
} else {
this
}
private fun File.zipStream() = ZipInputStream(inputStream())
}