Added new CompoundDownloadService which will encapsulate all downloads and should manage post-download events as well
As of now it's still in a development stage and isn't connected to anything
This commit is contained in:
parent
8af832a496
commit
9542ca773f
@ -9,8 +9,8 @@
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:name="a.e"
|
||||
android:allowBackup="true"
|
||||
android:theme="@style/MagiskTheme"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
|
||||
@ -41,9 +41,9 @@
|
||||
|
||||
<activity
|
||||
android:name="a.m"
|
||||
android:exported="false"
|
||||
android:directBootAware="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="false"
|
||||
android:theme="@style/MagiskTheme.SU" />
|
||||
|
||||
<!-- Receiver -->
|
||||
@ -66,6 +66,9 @@
|
||||
<!-- Service -->
|
||||
|
||||
<service android:name="a.j" />
|
||||
<service
|
||||
android:name="a.k"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- Hardcode GMS version -->
|
||||
<meta-data
|
||||
|
5
app/src/main/java/a/k.kt
Normal file
5
app/src/main/java/a/k.kt
Normal file
@ -0,0 +1,5 @@
|
||||
package a
|
||||
|
||||
import com.topjohnwu.magisk.model.download.CompoundDownloadService
|
||||
|
||||
class k : CompoundDownloadService()
|
@ -1,5 +1,6 @@
|
||||
package com.topjohnwu.magisk
|
||||
|
||||
import com.topjohnwu.magisk.model.download.CompoundDownloadService
|
||||
import com.topjohnwu.magisk.model.download.DownloadModuleService
|
||||
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
|
||||
import com.topjohnwu.magisk.model.update.UpdateCheckService
|
||||
@ -17,6 +18,7 @@ object ClassMap {
|
||||
UpdateCheckService::class.java to a.g::class.java,
|
||||
GeneralReceiver::class.java to a.h::class.java,
|
||||
DownloadModuleService::class.java to a.j::class.java,
|
||||
CompoundDownloadService::class.java to a.k::class.java,
|
||||
SuRequestActivity::class.java to a.m::class.java
|
||||
)
|
||||
|
||||
|
@ -42,6 +42,7 @@ object Config : PreferenceModel, DBConfig {
|
||||
const val ETAG_KEY = "ETag"
|
||||
const val REPO_ORDER = "repo_order"
|
||||
const val SHOW_SYSTEM_APP = "show_system"
|
||||
const val DOWNLOAD_CACHE = "download_cache"
|
||||
|
||||
// system state
|
||||
const val MAGISKHIDE = "magiskhide"
|
||||
@ -94,6 +95,7 @@ object Config : PreferenceModel, DBConfig {
|
||||
if (Utils.isCanary) Value.CANARY_DEBUG_CHANNEL
|
||||
else Value.DEFAULT_CHANNEL
|
||||
|
||||
var isDownloadCacheEnabled by preference(Key.DOWNLOAD_CACHE, true)
|
||||
var repoOrder by preference(Key.REPO_ORDER, Value.ORDER_DATE)
|
||||
|
||||
var suDefaultTimeout by preferenceStrInt(Key.SU_REQUEST_TIMEOUT, 10)
|
||||
|
@ -0,0 +1,11 @@
|
||||
package com.topjohnwu.magisk.data.repository
|
||||
|
||||
import com.topjohnwu.magisk.data.network.GithubRawApiServices
|
||||
|
||||
class FileRepository(
|
||||
private val api: GithubRawApiServices
|
||||
) {
|
||||
|
||||
fun downloadFile(url: String) = api.fetchFile(url)
|
||||
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package com.topjohnwu.magisk.di
|
||||
|
||||
import com.topjohnwu.magisk.data.repository.AppRepository
|
||||
import com.topjohnwu.magisk.data.repository.FileRepository
|
||||
import com.topjohnwu.magisk.data.repository.LogRepository
|
||||
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||
import org.koin.dsl.module
|
||||
@ -10,4 +11,5 @@ val repositoryModule = module {
|
||||
single { MagiskRepository(get(), get(), get()) }
|
||||
single { LogRepository(get()) }
|
||||
single { AppRepository(get()) }
|
||||
single { FileRepository(get()) }
|
||||
}
|
||||
|
@ -0,0 +1,75 @@
|
||||
package com.topjohnwu.magisk.model.download
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.topjohnwu.magisk.ClassMap
|
||||
import com.topjohnwu.magisk.model.entity.internal.Configuration
|
||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||
import com.topjohnwu.magisk.ui.flash.FlashActivity
|
||||
import java.io.File
|
||||
import kotlin.random.Random.Default.nextInt
|
||||
|
||||
@SuppressLint("Registered")
|
||||
open class CompoundDownloadService : SubstrateDownloadService() {
|
||||
|
||||
private val context get() = this
|
||||
|
||||
override fun onFinished(file: File, subject: DownloadSubject) {
|
||||
when (subject) {
|
||||
is DownloadSubject.Magisk -> {
|
||||
if (subject.configuration == Configuration.FLASH) {
|
||||
FlashActivity.flashMagisk(this, file)
|
||||
}
|
||||
}
|
||||
is DownloadSubject.Module -> {
|
||||
if (subject.configuration == Configuration.FLASH) {
|
||||
FlashActivity.flashModule(this, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
override fun NotificationCompat.Builder.addActions(file: File, subject: DownloadSubject) =
|
||||
when (subject) {
|
||||
is DownloadSubject.Magisk -> addMagiskActions(file, subject.configuration)
|
||||
is DownloadSubject.Module -> addModuleActions(file, subject.configuration)
|
||||
}
|
||||
|
||||
private fun NotificationCompat.Builder.addMagiskActions(
|
||||
file: File,
|
||||
configuration: Configuration
|
||||
) = apply {
|
||||
when (configuration) {
|
||||
Configuration.FLASH -> {
|
||||
val inner = FlashActivity.flashMagiskIntent(context, file)
|
||||
val intent = PendingIntent
|
||||
.getActivity(context, nextInt(), inner, PendingIntent.FLAG_ONE_SHOT)
|
||||
|
||||
setContentIntent(intent)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun NotificationCompat.Builder.addModuleActions(
|
||||
file: File,
|
||||
configuration: Configuration
|
||||
) = apply {
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun download(context: Context, subject: DownloadSubject) =
|
||||
Intent(context, ClassMap[CompoundDownloadService::class.java])
|
||||
.putExtra(ARG_URL, subject)
|
||||
.let { context.startService(it); Unit }
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
package com.topjohnwu.magisk.model.download
|
||||
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import com.skoumal.teanity.extensions.subscribeK
|
||||
import com.topjohnwu.magisk.Config
|
||||
import com.topjohnwu.magisk.Const
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.data.repository.FileRepository
|
||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||
import com.topjohnwu.magisk.utils.writeToCachedFile
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import com.topjohnwu.superuser.ShellUtils
|
||||
import io.reactivex.Single
|
||||
import okhttp3.ResponseBody
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.io.File
|
||||
import kotlin.random.Random.Default.nextInt
|
||||
|
||||
abstract class SubstrateDownloadService : Service() {
|
||||
|
||||
private var _notification: NotificationCompat.Builder? = null
|
||||
|
||||
private val repo by inject<FileRepository>()
|
||||
|
||||
private val notification: NotificationCompat.Builder
|
||||
get() = _notification ?: Notifications.progress(this, "")
|
||||
.setContentText(getString(R.string.download_local))
|
||||
.also { _notification = it }
|
||||
|
||||
override fun onBind(p0: Intent?): IBinder? = null
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
intent?.getParcelableExtra<DownloadSubject>(ARG_URL)?.let {
|
||||
updateNotification { notification -> notification.setContentTitle(it.fileName) }
|
||||
start(it)
|
||||
}
|
||||
return START_REDELIVER_INTENT
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
private fun start(subject: DownloadSubject) = search(subject)
|
||||
.onErrorResumeNext(download(subject))
|
||||
.subscribeK {
|
||||
runCatching { onFinished(it, subject) }
|
||||
finish(it, subject)
|
||||
}
|
||||
|
||||
private fun search(subject: DownloadSubject) = Single.fromCallable {
|
||||
if (!Config.isDownloadCacheEnabled) {
|
||||
throw IllegalStateException("The download cache is disabled")
|
||||
}
|
||||
|
||||
val file = runCatching {
|
||||
cacheDir.list().orEmpty()
|
||||
.first { it == subject.fileName } // this throws an exception if not found
|
||||
.let { File(cacheDir, it) }
|
||||
}.getOrElse {
|
||||
Const.EXTERNAL_PATH.list().orEmpty()
|
||||
.first { it == subject.fileName } // this throws an exception if not found
|
||||
.let { File(Const.EXTERNAL_PATH, it) }
|
||||
}
|
||||
|
||||
if (subject is DownloadSubject.Magisk) {
|
||||
if (!ShellUtils.checkSum("MD5", file, subject.magisk.hash)) {
|
||||
throw IllegalStateException("The given file doesn't match the hash")
|
||||
}
|
||||
}
|
||||
|
||||
file
|
||||
}
|
||||
|
||||
private fun download(subject: DownloadSubject) = repo.downloadFile(subject.url)
|
||||
.map { it.toFile(subject.fileName) }
|
||||
|
||||
// ---
|
||||
|
||||
private fun ResponseBody.toFile(name: String): File {
|
||||
val maxRaw = contentLength()
|
||||
val max = maxRaw / 1_000_000f
|
||||
|
||||
return writeToCachedFile(this@SubstrateDownloadService, name) {
|
||||
val progress = it / 1_000_000f
|
||||
|
||||
updateNotification { notification ->
|
||||
notification
|
||||
.setProgress(maxRaw.toInt(), it.toInt(), false)
|
||||
.setContentText(getString(R.string.download_progress, progress, max))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun finish(file: File, subject: DownloadSubject) {
|
||||
stopForeground(false)
|
||||
|
||||
val notification = notification.addActions(file, subject)
|
||||
.setContentText(getString(R.string.download_complete))
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
.setProgress(0, 0, false)
|
||||
.setOngoing(false)
|
||||
.setAutoCancel(true)
|
||||
.build()
|
||||
|
||||
getSystemService<NotificationManager>()?.notify(nextInt(), notification)
|
||||
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
private inline fun updateNotification(body: (NotificationCompat.Builder) -> Unit = {}) {
|
||||
startForeground(ID, notification.also(body).build())
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
|
||||
@Throws(Throwable::class)
|
||||
protected abstract fun onFinished(file: File, subject: DownloadSubject)
|
||||
|
||||
protected abstract fun NotificationCompat.Builder.addActions(
|
||||
file: File,
|
||||
subject: DownloadSubject
|
||||
): NotificationCompat.Builder
|
||||
|
||||
|
||||
companion object {
|
||||
private const val ID = 300
|
||||
const val ARG_URL = "arg_url"
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package com.topjohnwu.magisk.model.entity
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.squareup.moshi.Json
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import se.ansman.kotshi.JsonSerializable
|
||||
|
||||
@JsonSerializable
|
||||
@ -15,6 +17,7 @@ data class UninstallerJson(
|
||||
val link: String = ""
|
||||
)
|
||||
|
||||
@Parcelize
|
||||
@JsonSerializable
|
||||
data class MagiskJson(
|
||||
val version: String = "",
|
||||
@ -22,7 +25,7 @@ data class MagiskJson(
|
||||
val link: String = "",
|
||||
val note: String = "",
|
||||
@Json(name = "md5") val hash: String = ""
|
||||
)
|
||||
) : Parcelable
|
||||
|
||||
@JsonSerializable
|
||||
data class ManagerJson(
|
||||
|
@ -0,0 +1,5 @@
|
||||
package com.topjohnwu.magisk.model.entity.internal
|
||||
|
||||
enum class Configuration {
|
||||
FLASH, DOWNLOAD
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.topjohnwu.magisk.model.entity.internal
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.topjohnwu.magisk.model.entity.MagiskJson
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import com.topjohnwu.magisk.model.entity.Module as MagiskModule
|
||||
|
||||
sealed class DownloadSubject : Parcelable {
|
||||
|
||||
abstract val fileName: String
|
||||
abstract val url: String
|
||||
|
||||
@Parcelize
|
||||
data class Module(
|
||||
val module: MagiskModule,
|
||||
val configuration: Configuration
|
||||
) : DownloadSubject() {
|
||||
|
||||
override val url: String get() = module.path
|
||||
override val fileName: String get() = "${module.name}-v${module.version}(${module.versionCode}).zip"
|
||||
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class Magisk(
|
||||
val magisk: MagiskJson,
|
||||
val configuration: Configuration
|
||||
) : DownloadSubject() {
|
||||
|
||||
override val url: String get() = magisk.link
|
||||
override val fileName get() = "Magisk-v${magisk.version}(${magisk.versionCode}).zip"
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -71,6 +71,8 @@
|
||||
<string name="update_channel">Magisk Updates</string>
|
||||
<string name="progress_channel">Progress Notifications</string>
|
||||
<string name="download_complete">Download complete</string>
|
||||
<string name="download_local">Looking for local copies…</string>
|
||||
<string name="download_progress">%1$.2f / %2$.2f MB</string>
|
||||
<string name="download_file_error">Error downloading file</string>
|
||||
<string name="magisk_update_title">Magisk Update Available!</string>
|
||||
<string name="manager_update_title">Magisk Manager Update Available!</string>
|
||||
|
Loading…
Reference in New Issue
Block a user