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" />
|
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
|
||||||
android:name="a.e"
|
android:name="a.e"
|
||||||
|
android:allowBackup="true"
|
||||||
android:theme="@style/MagiskTheme"
|
android:theme="@style/MagiskTheme"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
|
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
|
||||||
@ -41,9 +41,9 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="a.m"
|
android:name="a.m"
|
||||||
android:exported="false"
|
|
||||||
android:directBootAware="true"
|
android:directBootAware="true"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
|
android:exported="false"
|
||||||
android:theme="@style/MagiskTheme.SU" />
|
android:theme="@style/MagiskTheme.SU" />
|
||||||
|
|
||||||
<!-- Receiver -->
|
<!-- Receiver -->
|
||||||
@ -66,6 +66,9 @@
|
|||||||
<!-- Service -->
|
<!-- Service -->
|
||||||
|
|
||||||
<service android:name="a.j" />
|
<service android:name="a.j" />
|
||||||
|
<service
|
||||||
|
android:name="a.k"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<!-- Hardcode GMS version -->
|
<!-- Hardcode GMS version -->
|
||||||
<meta-data
|
<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
|
package com.topjohnwu.magisk
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.model.download.CompoundDownloadService
|
||||||
import com.topjohnwu.magisk.model.download.DownloadModuleService
|
import com.topjohnwu.magisk.model.download.DownloadModuleService
|
||||||
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
|
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
|
||||||
import com.topjohnwu.magisk.model.update.UpdateCheckService
|
import com.topjohnwu.magisk.model.update.UpdateCheckService
|
||||||
@ -17,6 +18,7 @@ object ClassMap {
|
|||||||
UpdateCheckService::class.java to a.g::class.java,
|
UpdateCheckService::class.java to a.g::class.java,
|
||||||
GeneralReceiver::class.java to a.h::class.java,
|
GeneralReceiver::class.java to a.h::class.java,
|
||||||
DownloadModuleService::class.java to a.j::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
|
SuRequestActivity::class.java to a.m::class.java
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
const val ETAG_KEY = "ETag"
|
const val ETAG_KEY = "ETag"
|
||||||
const val REPO_ORDER = "repo_order"
|
const val REPO_ORDER = "repo_order"
|
||||||
const val SHOW_SYSTEM_APP = "show_system"
|
const val SHOW_SYSTEM_APP = "show_system"
|
||||||
|
const val DOWNLOAD_CACHE = "download_cache"
|
||||||
|
|
||||||
// system state
|
// system state
|
||||||
const val MAGISKHIDE = "magiskhide"
|
const val MAGISKHIDE = "magiskhide"
|
||||||
@ -94,6 +95,7 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
if (Utils.isCanary) Value.CANARY_DEBUG_CHANNEL
|
if (Utils.isCanary) Value.CANARY_DEBUG_CHANNEL
|
||||||
else Value.DEFAULT_CHANNEL
|
else Value.DEFAULT_CHANNEL
|
||||||
|
|
||||||
|
var isDownloadCacheEnabled by preference(Key.DOWNLOAD_CACHE, true)
|
||||||
var repoOrder by preference(Key.REPO_ORDER, Value.ORDER_DATE)
|
var repoOrder by preference(Key.REPO_ORDER, Value.ORDER_DATE)
|
||||||
|
|
||||||
var suDefaultTimeout by preferenceStrInt(Key.SU_REQUEST_TIMEOUT, 10)
|
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
|
package com.topjohnwu.magisk.di
|
||||||
|
|
||||||
import com.topjohnwu.magisk.data.repository.AppRepository
|
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.LogRepository
|
||||||
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
@ -10,4 +11,5 @@ val repositoryModule = module {
|
|||||||
single { MagiskRepository(get(), get(), get()) }
|
single { MagiskRepository(get(), get(), get()) }
|
||||||
single { LogRepository(get()) }
|
single { LogRepository(get()) }
|
||||||
single { AppRepository(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
|
package com.topjohnwu.magisk.model.entity
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
import se.ansman.kotshi.JsonSerializable
|
import se.ansman.kotshi.JsonSerializable
|
||||||
|
|
||||||
@JsonSerializable
|
@JsonSerializable
|
||||||
@ -15,6 +17,7 @@ data class UninstallerJson(
|
|||||||
val link: String = ""
|
val link: String = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
@JsonSerializable
|
@JsonSerializable
|
||||||
data class MagiskJson(
|
data class MagiskJson(
|
||||||
val version: String = "",
|
val version: String = "",
|
||||||
@ -22,7 +25,7 @@ data class MagiskJson(
|
|||||||
val link: String = "",
|
val link: String = "",
|
||||||
val note: String = "",
|
val note: String = "",
|
||||||
@Json(name = "md5") val hash: String = ""
|
@Json(name = "md5") val hash: String = ""
|
||||||
)
|
) : Parcelable
|
||||||
|
|
||||||
@JsonSerializable
|
@JsonSerializable
|
||||||
data class ManagerJson(
|
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="update_channel">Magisk Updates</string>
|
||||||
<string name="progress_channel">Progress Notifications</string>
|
<string name="progress_channel">Progress Notifications</string>
|
||||||
<string name="download_complete">Download complete</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="download_file_error">Error downloading file</string>
|
||||||
<string name="magisk_update_title">Magisk Update Available!</string>
|
<string name="magisk_update_title">Magisk Update Available!</string>
|
||||||
<string name="manager_update_title">Magisk Manager Update Available!</string>
|
<string name="manager_update_title">Magisk Manager Update Available!</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user