Commit 9542ca77 authored by Viktor De Pasquale's avatar Viktor De Pasquale Committed by John Wu

Added new CompoundDownloadService which will encapsulate all downloads and...

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
parent 8af832a4
......@@ -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
......
package a
import com.topjohnwu.magisk.model.download.CompoundDownloadService
class k : CompoundDownloadService()
\ No newline at end of file
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)
......
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)
}
\ No newline at end of file
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()) }
}
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 }
}
}
\ No newline at end of file
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"
}
}
\ No newline at end of file
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(
......
package com.topjohnwu.magisk.model.entity.internal
enum class Configuration {
FLASH, DOWNLOAD
}
\ No newline at end of file
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"
}
}
\ No newline at end of file
......@@ -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>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment