mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-11-23 18:15:30 +00:00
Isolate download logic from service lifecycle
This commit is contained in:
parent
154121f3dd
commit
c5d34670c4
@ -61,7 +61,7 @@
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".core.download.DownloadService"
|
||||
android:name=".core.Service"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
|
||||
|
40
app/src/main/java/com/topjohnwu/magisk/core/Service.kt
Normal file
40
app/src/main/java/com/topjohnwu/magisk/core/Service.kt
Normal file
@ -0,0 +1,40 @@
|
||||
package com.topjohnwu.magisk.core
|
||||
|
||||
import android.app.Notification
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.core.content.IntentCompat
|
||||
import com.topjohnwu.magisk.core.base.BaseService
|
||||
import com.topjohnwu.magisk.core.download.DownloadManager
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
|
||||
class Service : BaseService(), DownloadManager.Session {
|
||||
|
||||
private lateinit var dm: DownloadManager
|
||||
override val context get() = this
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
dm = DownloadManager(this)
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
if (intent.action == DownloadManager.ACTION) {
|
||||
IntentCompat
|
||||
.getParcelableExtra(intent, DownloadManager.SUBJECT_KEY, Subject::class.java)
|
||||
?.let { dm.download(it) }
|
||||
}
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun attach(id: Int, builder: Notification.Builder) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
builder.setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE)
|
||||
startForeground(id, builder.build())
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
||||
}
|
||||
}
|
@ -2,30 +2,41 @@ package com.topjohnwu.magisk.core.download
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Notification
|
||||
import android.app.PendingIntent
|
||||
import android.app.PendingIntent.*
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.collection.SparseArrayCompat
|
||||
import androidx.collection.isNotEmpty
|
||||
import androidx.core.net.toFile
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
import com.topjohnwu.magisk.core.ActivityTracker
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.base.BaseActivity
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.intent
|
||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
import com.topjohnwu.magisk.core.ktx.*
|
||||
import com.topjohnwu.magisk.core.ktx.copyAndClose
|
||||
import com.topjohnwu.magisk.core.ktx.forEach
|
||||
import com.topjohnwu.magisk.core.ktx.selfLaunchIntent
|
||||
import com.topjohnwu.magisk.core.ktx.set
|
||||
import com.topjohnwu.magisk.core.ktx.withStreams
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.tasks.HideAPK
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.core.utils.ProgressInputStream
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.ResponseBody
|
||||
import timber.log.Timber
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
@ -37,27 +48,77 @@ import java.util.zip.ZipFile
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
class DownloadService : NotificationService() {
|
||||
class DownloadManager(
|
||||
private val session: Session
|
||||
) {
|
||||
|
||||
private val job = Job()
|
||||
interface Session {
|
||||
val context: Context
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
intent.getParcelableExtra<Subject>(SUBJECT_KEY)?.let { download(it) }
|
||||
return START_NOT_STICKY
|
||||
fun attach(id: Int, notification: Notification.Builder)
|
||||
fun stop()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
job.cancel()
|
||||
companion object {
|
||||
const val ACTION = "com.topjohnwu.magisk.DOWNLOAD"
|
||||
const val SUBJECT_KEY = "subject"
|
||||
private const val REQUEST_CODE = 1
|
||||
|
||||
private val progressBroadcast = MutableLiveData<Pair<Float, Subject>?>()
|
||||
|
||||
private fun broadcast(progress: Float, subject: Subject) {
|
||||
progressBroadcast.postValue(progress to subject)
|
||||
}
|
||||
|
||||
fun observeProgress(owner: LifecycleOwner, callback: (Float, Subject) -> Unit) {
|
||||
progressBroadcast.value = null
|
||||
progressBroadcast.observe(owner) {
|
||||
val (progress, subject) = it ?: return@observe
|
||||
callback(progress, subject)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createIntent(context: Context, subject: Subject) =
|
||||
context.intent<com.topjohnwu.magisk.core.Service>()
|
||||
.setAction(ACTION)
|
||||
.putExtra(SUBJECT_KEY, subject)
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
fun getPendingIntent(context: Context, subject: Subject): PendingIntent {
|
||||
val flag = PendingIntent.FLAG_IMMUTABLE or
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or
|
||||
PendingIntent.FLAG_ONE_SHOT
|
||||
val intent = createIntent(context, subject)
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
PendingIntent.getForegroundService(context, REQUEST_CODE, intent, flag)
|
||||
} else {
|
||||
PendingIntent.getService(context, REQUEST_CODE, intent, flag)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
fun start(activity: BaseActivity, subject: Subject) {
|
||||
activity.withPermission(Manifest.permission.POST_NOTIFICATIONS) {
|
||||
// Always download regardless of notification permission status
|
||||
val app = activity.applicationContext
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
app.startForegroundService(createIntent(app, subject))
|
||||
} else {
|
||||
app.startService(createIntent(app, subject))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun download(subject: Subject) {
|
||||
fun download(subject: Subject) {
|
||||
notifyUpdate(subject.notifyId)
|
||||
CoroutineScope(job + Dispatchers.IO).launch {
|
||||
try {
|
||||
val stream = service.fetchFile(subject.url).toProgressStream(subject)
|
||||
val stream = network.fetchFile(subject.url).toProgressStream(subject)
|
||||
when (subject) {
|
||||
is Subject.App -> handleApp(stream, subject)
|
||||
is Subject.Module -> handleModule(stream, subject.file)
|
||||
is Subject.Test -> stream.copyAndClose(subject.file.outputStream())
|
||||
}
|
||||
val activity = ActivityTracker.foreground
|
||||
if (activity != null && subject.autoLaunch) {
|
||||
@ -67,15 +128,99 @@ class DownloadService : NotificationService() {
|
||||
notifyFinish(subject)
|
||||
}
|
||||
subject.postDownload?.invoke()
|
||||
if (!hasNotifications)
|
||||
stopSelf()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
notifyFail(subject)
|
||||
}
|
||||
|
||||
synchronized(this@DownloadManager) {
|
||||
if (notifications.isEmpty)
|
||||
session.stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val notifications = SparseArrayCompat<Notification.Builder>()
|
||||
private var attachedId = -1
|
||||
|
||||
private val job = Job()
|
||||
|
||||
private val context get() = session.context
|
||||
private val network get() = ServiceLocator.networkService
|
||||
|
||||
private fun finalNotify(id: Int, editor: (Notification.Builder) -> Unit): Int {
|
||||
val notification = notifyRemove(id)?.also(editor) ?: return -1
|
||||
val newId = Notifications.nextId()
|
||||
Notifications.mgr.notify(newId, notification.build())
|
||||
return newId
|
||||
}
|
||||
|
||||
private fun notifyFail(subject: Subject) = finalNotify(subject.notifyId) {
|
||||
broadcast(-2f, subject)
|
||||
it.setContentText(context.getString(R.string.download_file_error))
|
||||
.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||
.setOngoing(false)
|
||||
}
|
||||
|
||||
private fun notifyFinish(subject: Subject) = finalNotify(subject.notifyId) {
|
||||
broadcast(1f, subject)
|
||||
it.setContentTitle(subject.title)
|
||||
.setContentText(context.getString(R.string.download_complete))
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
.setProgress(0, 0, false)
|
||||
.setOngoing(false)
|
||||
.setAutoCancel(true)
|
||||
subject.pendingIntent(context)?.let { intent -> it.setContentIntent(intent) }
|
||||
}
|
||||
|
||||
private fun attachNotification(id: Int, notification: Notification.Builder) {
|
||||
attachedId = id
|
||||
session.attach(id, notification)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit = {}) {
|
||||
val notification = (notifications[id] ?: Notifications.startProgress("").also {
|
||||
notifications[id] = it
|
||||
}).apply(editor)
|
||||
|
||||
if (attachedId < 0)
|
||||
attachNotification(id, notification)
|
||||
else
|
||||
Notifications.mgr.notify(id, notification.build())
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun notifyRemove(id: Int): Notification.Builder? {
|
||||
val idx = notifications.indexOfKey(id)
|
||||
var n: Notification.Builder? = null
|
||||
|
||||
if (idx >= 0) {
|
||||
n = notifications.valueAt(idx)
|
||||
notifications.removeAt(idx)
|
||||
|
||||
// The cancelled notification is the one attached to the session, need special handling
|
||||
if (attachedId == id) {
|
||||
if (notifications.isNotEmpty()) {
|
||||
// There are still remaining notifications, pick one and attach to the session
|
||||
val anotherId = notifications.keyAt(0)
|
||||
val notification = notifications.valueAt(0)
|
||||
// Attaching a new notification will automatically remove the current one
|
||||
attachNotification(anotherId, notification)
|
||||
} else {
|
||||
// No more notifications left, terminate the session
|
||||
attachedId = -1
|
||||
session.stop()
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
}
|
||||
|
||||
Notifications.mgr.cancel(id)
|
||||
return n
|
||||
}
|
||||
|
||||
private fun handleApp(stream: InputStream, subject: Subject.App) {
|
||||
fun writeTee(output: OutputStream) {
|
||||
val uri = MediaStoreUtils.getFile("${subject.title}.apk").uri
|
||||
@ -84,7 +229,7 @@ class DownloadService : NotificationService() {
|
||||
}
|
||||
|
||||
if (isRunningAsStub) {
|
||||
val updateApk = StubApk.update(this)
|
||||
val updateApk = StubApk.update(context)
|
||||
try {
|
||||
// Download full APK to stub update path
|
||||
writeTee(updateApk.outputStream())
|
||||
@ -97,7 +242,7 @@ class DownloadService : NotificationService() {
|
||||
// Also upgrade stub
|
||||
notifyUpdate(subject.notifyId) {
|
||||
it.setProgress(0, 0, true)
|
||||
.setContentTitle(getString(R.string.hide_app_title))
|
||||
.setContentTitle(context.getString(R.string.hide_app_title))
|
||||
.setContentText("")
|
||||
}
|
||||
|
||||
@ -107,7 +252,7 @@ class DownloadService : NotificationService() {
|
||||
zf.close()
|
||||
|
||||
// Patch and install
|
||||
subject.intent = HideAPK.upgrade(this, apk)
|
||||
subject.intent = HideAPK.upgrade(context, apk)
|
||||
?: throw IOException("HideAPK patch error")
|
||||
apk.delete()
|
||||
} else {
|
||||
@ -116,7 +261,7 @@ class DownloadService : NotificationService() {
|
||||
StubApk.restartProcess(it)
|
||||
} ?: run {
|
||||
// Or else kill the current process after posting notification
|
||||
subject.intent = selfLaunchIntent()
|
||||
subject.intent = context.selfLaunchIntent()
|
||||
subject.postDownload = { Runtime.getRuntime().exit(0) }
|
||||
}
|
||||
return
|
||||
@ -127,8 +272,8 @@ class DownloadService : NotificationService() {
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
val session = APKInstall.startSession(this)
|
||||
writeTee(session.openStream(this))
|
||||
val session = APKInstall.startSession(context)
|
||||
writeTee(session.openStream(context))
|
||||
subject.intent = session.waitIntent()
|
||||
}
|
||||
}
|
||||
@ -143,7 +288,7 @@ class DownloadService : NotificationService() {
|
||||
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"))
|
||||
assets.open("module_installer.sh").copyTo(zout)
|
||||
context.assets.open("module_installer.sh").copyTo(zout)
|
||||
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script"))
|
||||
zout.write("#MAGISK\n".toByteArray())
|
||||
@ -178,41 +323,24 @@ class DownloadService : NotificationService() {
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SUBJECT_KEY = "subject"
|
||||
private const val REQUEST_CODE = 1
|
||||
private fun ResponseBody.toProgressStream(subject: Subject): InputStream {
|
||||
val max = contentLength()
|
||||
val total = max.toFloat() / 1048576
|
||||
val id = subject.notifyId
|
||||
|
||||
fun observeProgress(owner: LifecycleOwner, callback: (Float, Subject) -> Unit) {
|
||||
progressBroadcast.value = null
|
||||
progressBroadcast.observe(owner) {
|
||||
val (progress, subject) = it ?: return@observe
|
||||
callback(progress, subject)
|
||||
}
|
||||
}
|
||||
notifyUpdate(id) { it.setContentTitle(subject.title) }
|
||||
|
||||
private fun intent(context: Context, subject: Subject) =
|
||||
context.intent<DownloadService>().putExtra(SUBJECT_KEY, subject)
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
fun getPendingIntent(context: Context, subject: Subject): PendingIntent {
|
||||
val flag = FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT or FLAG_ONE_SHOT
|
||||
val intent = intent(context, subject)
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
getForegroundService(context, REQUEST_CODE, intent, flag)
|
||||
} else {
|
||||
getService(context, REQUEST_CODE, intent, flag)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
fun start(activity: BaseActivity, subject: Subject) {
|
||||
activity.withPermission(Manifest.permission.POST_NOTIFICATIONS) {
|
||||
// Always download regardless of notification permission status
|
||||
val app = activity.applicationContext
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
app.startForegroundService(intent(app, subject))
|
||||
return ProgressInputStream(byteStream()) {
|
||||
val progress = it.toFloat() / 1048576
|
||||
notifyUpdate(id) { notification ->
|
||||
if (max > 0) {
|
||||
broadcast(progress / total, subject)
|
||||
notification
|
||||
.setProgress(max.toInt(), it.toInt(), false)
|
||||
.setContentText("%.2f / %.2f MB".format(progress, total))
|
||||
} else {
|
||||
app.startService(intent(app, subject))
|
||||
broadcast(-1f, subject)
|
||||
notification.setContentText("%.2f MB / ??".format(progress))
|
||||
}
|
||||
}
|
||||
}
|
@ -1,128 +0,0 @@
|
||||
package com.topjohnwu.magisk.core.download
|
||||
|
||||
import android.app.Notification
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.base.BaseService
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.ktx.synchronized
|
||||
import com.topjohnwu.magisk.core.utils.ProgressInputStream
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import okhttp3.ResponseBody
|
||||
import java.io.InputStream
|
||||
|
||||
open class NotificationService : BaseService() {
|
||||
|
||||
private val notifications = HashMap<Int, Notification.Builder>().synchronized()
|
||||
protected val hasNotifications get() = notifications.isNotEmpty()
|
||||
|
||||
protected val service get() = ServiceLocator.networkService
|
||||
|
||||
private var attachedNotificationId = 0
|
||||
|
||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||
super.onTaskRemoved(rootIntent)
|
||||
notifications.forEach { Notifications.mgr.cancel(it.key) }
|
||||
notifications.clear()
|
||||
}
|
||||
|
||||
protected fun ResponseBody.toProgressStream(subject: Subject): InputStream {
|
||||
val max = contentLength()
|
||||
val total = max.toFloat() / 1048576
|
||||
val id = subject.notifyId
|
||||
|
||||
notifyUpdate(id) { it.setContentTitle(subject.title) }
|
||||
|
||||
return ProgressInputStream(byteStream()) {
|
||||
val progress = it.toFloat() / 1048576
|
||||
notifyUpdate(id) { notification ->
|
||||
if (max > 0) {
|
||||
broadcast(progress / total, subject)
|
||||
notification
|
||||
.setProgress(max.toInt(), it.toInt(), false)
|
||||
.setContentText("%.2f / %.2f MB".format(progress, total))
|
||||
} else {
|
||||
broadcast(-1f, subject)
|
||||
notification.setContentText("%.2f MB / ??".format(progress))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun finalNotify(id: Int, editor: (Notification.Builder) -> Unit): Int {
|
||||
val notification = notifyRemove(id)?.also(editor) ?: return -1
|
||||
val newId = Notifications.nextId()
|
||||
Notifications.mgr.notify(newId, notification.build())
|
||||
return newId
|
||||
}
|
||||
|
||||
protected fun notifyFail(subject: Subject) = finalNotify(subject.notifyId) {
|
||||
broadcast(-2f, subject)
|
||||
it.setContentText(getString(R.string.download_file_error))
|
||||
.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||
.setOngoing(false)
|
||||
}
|
||||
|
||||
protected fun notifyFinish(subject: Subject) = finalNotify(subject.notifyId) {
|
||||
broadcast(1f, subject)
|
||||
it.setContentTitle(subject.title)
|
||||
.setContentText(getString(R.string.download_complete))
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
.setProgress(0, 0, false)
|
||||
.setOngoing(false)
|
||||
.setAutoCancel(true)
|
||||
subject.pendingIntent(this)?.let { intent -> it.setContentIntent(intent) }
|
||||
}
|
||||
|
||||
private fun attachNotification(id: Int, notification: Notification) {
|
||||
attachedNotificationId = id
|
||||
startForeground(id, notification)
|
||||
}
|
||||
|
||||
private fun maybeDetachNotification(id: Int) : Boolean {
|
||||
if (attachedNotificationId != id) return false
|
||||
if (hasNotifications) {
|
||||
val (anotherId, notification) = notifications.entries.first()
|
||||
// Attaching a new notification will remove the current showing one
|
||||
attachNotification(anotherId, notification.build())
|
||||
return true
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
stopForeground(true)
|
||||
}
|
||||
attachedNotificationId = 0
|
||||
return true
|
||||
}
|
||||
|
||||
protected fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit = {}) {
|
||||
fun create() = Notifications.startProgress("")
|
||||
|
||||
val wasEmpty = !hasNotifications
|
||||
val notification = notifications.getOrPut(id, ::create).also(editor).build()
|
||||
if (wasEmpty)
|
||||
attachNotification(id, notification)
|
||||
else
|
||||
Notifications.mgr.notify(id, notification)
|
||||
}
|
||||
|
||||
protected fun notifyRemove(id: Int): Notification.Builder? {
|
||||
val n = notifications.remove(id)
|
||||
if (n == null || !maybeDetachNotification(id))
|
||||
Notifications.mgr.cancel(id)
|
||||
return n
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
protected val progressBroadcast = MutableLiveData<Pair<Float, Subject>?>()
|
||||
|
||||
private fun broadcast(progress: Float, subject: Subject) {
|
||||
progressBroadcast.postValue(progress to subject)
|
||||
}
|
||||
}
|
||||
}
|
@ -17,8 +17,8 @@ import com.topjohnwu.magisk.ui.flash.FlashFragment
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
private fun cachedFile(name: String) = AppContext.cachedFile(name).apply { delete() }.toUri()
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
|
||||
enum class Action {
|
||||
Flash,
|
||||
@ -34,7 +34,7 @@ sealed class Subject : Parcelable {
|
||||
open val autoLaunch: Boolean get() = true
|
||||
open val postDownload: (() -> Unit)? get() = null
|
||||
|
||||
abstract fun pendingIntent(context: Context): PendingIntent?
|
||||
open fun pendingIntent(context: Context): PendingIntent? = null
|
||||
|
||||
@Parcelize
|
||||
class Module(
|
||||
@ -65,7 +65,7 @@ sealed class Subject : Parcelable {
|
||||
|
||||
@IgnoredOnParcel
|
||||
override val file by lazy {
|
||||
cachedFile("manager.apk")
|
||||
AppContext.cachedFile("manager.apk").apply { delete() }.toUri()
|
||||
}
|
||||
|
||||
@IgnoredOnParcel
|
||||
@ -76,6 +76,16 @@ sealed class Subject : Parcelable {
|
||||
override fun pendingIntent(context: Context) = intent?.toPending(context)
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
class Test(
|
||||
override val notifyId: Int = Notifications.nextId(),
|
||||
override val title: String = UUID.randomUUID().toString().substring(0, 6)
|
||||
) : Subject() {
|
||||
override val url get() = "http://link.testfile.org/150MB"
|
||||
override val file get() = File("/dev/null").toUri()
|
||||
override val autoLaunch get() = false
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
protected fun Intent.toPending(context: Context): PendingIntent {
|
||||
return PendingIntent.getActivity(context, notifyId, this,
|
||||
|
@ -4,7 +4,7 @@ import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.di.AppContext
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.download.DownloadService
|
||||
import com.topjohnwu.magisk.core.download.DownloadManager
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import java.io.File
|
||||
@ -29,7 +29,7 @@ class ManagerInstallDialog : MarkDownDialog() {
|
||||
setCancelable(true)
|
||||
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
||||
text = R.string.install
|
||||
onClick { DownloadService.start(activity, Subject.App()) }
|
||||
onClick { DownloadManager.start(activity, Subject.App()) }
|
||||
}
|
||||
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
||||
text = android.R.string.cancel
|
||||
|
@ -3,7 +3,7 @@ package com.topjohnwu.magisk.dialog
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.download.Action
|
||||
import com.topjohnwu.magisk.core.download.DownloadService
|
||||
import com.topjohnwu.magisk.core.download.DownloadManager
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
import com.topjohnwu.magisk.core.model.module.OnlineModule
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
@ -24,7 +24,7 @@ class OnlineModuleInstallDialog(private val item: OnlineModule) : MarkDownDialog
|
||||
fun download(install: Boolean) {
|
||||
val action = if (install) Action.Flash else Action.Download
|
||||
val subject = Subject.Module(item, action)
|
||||
DownloadService.start(activity, subject)
|
||||
DownloadManager.start(activity, subject)
|
||||
}
|
||||
|
||||
val title = context.getString(R.string.repo_install_title,
|
||||
|
@ -14,7 +14,7 @@ import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseFragment
|
||||
import com.topjohnwu.magisk.arch.viewModel
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.download.DownloadService
|
||||
import com.topjohnwu.magisk.core.download.DownloadManager
|
||||
import com.topjohnwu.magisk.databinding.FragmentHomeMd2Binding
|
||||
|
||||
class HomeFragment : BaseFragment<FragmentHomeMd2Binding>(), MenuProvider {
|
||||
@ -25,7 +25,7 @@ class HomeFragment : BaseFragment<FragmentHomeMd2Binding>(), MenuProvider {
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
activity?.setTitle(R.string.section_home)
|
||||
DownloadService.observeProgress(this, viewModel::onProgressUpdate)
|
||||
DownloadManager.observeProgress(this, viewModel::onProgressUpdate)
|
||||
}
|
||||
|
||||
private fun checkTitle(text: TextView, icon: ImageView) {
|
||||
|
@ -11,7 +11,7 @@ import androidx.core.content.getSystemService
|
||||
import androidx.core.graphics.drawable.toIcon
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.di.AppContext
|
||||
import com.topjohnwu.magisk.core.download.DownloadService
|
||||
import com.topjohnwu.magisk.core.download.DownloadManager
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
import com.topjohnwu.magisk.core.ktx.getBitmap
|
||||
import com.topjohnwu.magisk.core.ktx.selfLaunchIntent
|
||||
@ -67,7 +67,7 @@ object Notifications {
|
||||
|
||||
fun updateAvailable() {
|
||||
AppContext.apply {
|
||||
val intent = DownloadService.getPendingIntent(this, Subject.App())
|
||||
val intent = DownloadManager.getPendingIntent(this, Subject.App())
|
||||
val bitmap = getBitmap(R.drawable.ic_magisk_outline)
|
||||
val builder = if (SDK_INT >= Build.VERSION_CODES.O) {
|
||||
Notification.Builder(this, UPDATE_CHANNEL)
|
||||
|
Loading…
Reference in New Issue
Block a user