mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-02-19 20:08:30 +00:00
Cleanup DownloadService
This commit is contained in:
parent
e660fabc57
commit
6dbd8baa7e
@ -39,8 +39,6 @@ object Const {
|
||||
}
|
||||
|
||||
object ID {
|
||||
// notifications
|
||||
const val APK_UPDATE_NOTIFICATION_ID = 5
|
||||
const val JOB_SERVICE_ID = 7
|
||||
const val UPDATE_NOTIFICATION_CHANNEL = "update"
|
||||
const val PROGRESS_NOTIFICATION_CHANNEL = "progress"
|
||||
|
@ -3,66 +3,47 @@ package com.topjohnwu.magisk.core.download
|
||||
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.os.Build
|
||||
import android.os.IBinder
|
||||
import androidx.core.net.toFile
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseUIActivity
|
||||
import com.topjohnwu.magisk.core.ForegroundTracker
|
||||
import com.topjohnwu.magisk.core.base.BaseService
|
||||
import com.topjohnwu.magisk.core.download.Action.Flash
|
||||
import com.topjohnwu.magisk.core.download.Subject.Manager
|
||||
import com.topjohnwu.magisk.core.download.Subject.Module
|
||||
import com.topjohnwu.magisk.core.intent
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.core.utils.ProgressInputStream
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.ui.flash.FlashFragment
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import com.topjohnwu.magisk.ktx.copyAndClose
|
||||
import com.topjohnwu.magisk.ktx.synchronized
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import com.topjohnwu.magisk.view.Notifications.mgr
|
||||
import kotlinx.coroutines.*
|
||||
import okhttp3.ResponseBody
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.random.Random.Default.nextInt
|
||||
|
||||
class DownloadService : BaseService() {
|
||||
|
||||
private val context get() = this
|
||||
private val hasNotifications get() = notifications.isNotEmpty()
|
||||
private val notifications = Collections.synchronizedMap(HashMap<Int, Notification.Builder>())
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.IO)
|
||||
private val notifications = HashMap<Int, Notification.Builder>().synchronized()
|
||||
private val job = Job()
|
||||
|
||||
val service get() = ServiceLocator.networkService
|
||||
private val mgr get() = Notifications.mgr
|
||||
|
||||
// -- Service overrides
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
intent.getParcelableExtra<Subject>(SUBJECT_KEY)?.let { subject ->
|
||||
update(subject.notifyID())
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
subject.startDownload()
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
notifyFail(subject)
|
||||
}
|
||||
}
|
||||
}
|
||||
return START_REDELIVER_INTENT
|
||||
intent.getParcelableExtra<Subject>(SUBJECT_KEY)?.let { doDownload(it) }
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||
@ -73,29 +54,40 @@ class DownloadService : BaseService() {
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
coroutineScope.cancel()
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
// -- Download logic
|
||||
|
||||
private suspend fun Subject.startDownload() {
|
||||
val stream = service.fetchFile(url).toProgressStream(this)
|
||||
when (this) {
|
||||
is Module -> // Download and process on-the-fly
|
||||
stream.toModule(file, service.fetchInstaller().byteStream())
|
||||
is Manager -> handleAPK(this, stream)
|
||||
private fun doDownload(subject: Subject) {
|
||||
update(subject.notifyId)
|
||||
val coroutineScope = CoroutineScope(job + Dispatchers.IO)
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
val stream = service.fetchFile(subject.url).toProgressStream(subject)
|
||||
when (subject) {
|
||||
is Subject.Manager -> handleAPK(subject, stream)
|
||||
else -> stream.copyAndClose(subject.file.outputStream())
|
||||
}
|
||||
if (ForegroundTracker.hasForeground) {
|
||||
remove(subject.notifyId)
|
||||
subject.pendingIntent(this@DownloadService).send()
|
||||
} else {
|
||||
notifyFinish(subject)
|
||||
}
|
||||
if (!hasNotifications)
|
||||
stopSelf()
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
notifyFail(subject)
|
||||
}
|
||||
}
|
||||
val newId = notifyFinish(this)
|
||||
if (ForegroundTracker.hasForeground)
|
||||
onFinish(this, newId)
|
||||
if (!hasNotifications)
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
private fun ResponseBody.toProgressStream(subject: Subject): InputStream {
|
||||
val max = contentLength()
|
||||
val total = max.toFloat() / 1048576
|
||||
val id = subject.notifyID()
|
||||
val id = subject.notifyId
|
||||
|
||||
update(id) { it.setContentTitle(subject.title) }
|
||||
|
||||
@ -115,18 +107,18 @@ class DownloadService : BaseService() {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Notifications
|
||||
// --- Notification management
|
||||
|
||||
private fun notifyFail(subject: Subject) = finalNotify(subject.notifyID()) {
|
||||
private 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)
|
||||
}
|
||||
|
||||
private fun notifyFinish(subject: Subject) = finalNotify(subject.notifyID()) {
|
||||
private fun notifyFinish(subject: Subject) = finalNotify(subject.notifyId) {
|
||||
broadcast(1f, subject)
|
||||
it.setIntent(subject)
|
||||
it.setContentIntent(subject.pendingIntent(this))
|
||||
.setContentTitle(subject.title)
|
||||
.setContentText(getString(R.string.download_complete))
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
@ -135,17 +127,14 @@ class DownloadService : BaseService() {
|
||||
.setAutoCancel(true)
|
||||
}
|
||||
|
||||
private fun finalNotify(
|
||||
id: Int,
|
||||
editor: (Notification.Builder) -> Notification.Builder
|
||||
) : Int {
|
||||
val notification = remove(id)?.run(editor) ?: return -1
|
||||
val newId = nextInt()
|
||||
private fun finalNotify(id: Int, editor: (Notification.Builder) -> Unit): Int {
|
||||
val notification = remove(id)?.also(editor) ?: return -1
|
||||
val newId = Notifications.nextId()
|
||||
mgr.notify(newId, notification.build())
|
||||
return newId
|
||||
}
|
||||
|
||||
fun create() = Notifications.progress(this, "")
|
||||
private fun create() = Notifications.progress(this, "")
|
||||
|
||||
fun update(id: Int, editor: (Notification.Builder) -> Unit = {}) {
|
||||
val wasEmpty = !hasNotifications
|
||||
@ -171,49 +160,9 @@ class DownloadService : BaseService() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun Notification.Builder.setIntent(subject: Subject) = when (subject) {
|
||||
is Module -> setIntent(subject)
|
||||
is Manager -> setIntent(subject)
|
||||
}
|
||||
|
||||
private fun Notification.Builder.setIntent(subject: Module)
|
||||
= when (subject.action) {
|
||||
Flash -> setContentIntent(FlashFragment.installIntent(context, subject.file))
|
||||
else -> setContentIntent(Intent())
|
||||
}
|
||||
|
||||
private fun Notification.Builder.setIntent(subject: Manager) =
|
||||
setContentIntent(APKInstall.installIntent(context, subject.file.toFile()))
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
private fun Notification.Builder.setContentIntent(intent: Intent) =
|
||||
setContentIntent(PendingIntent.getActivity(context, nextInt(), intent,
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_ONE_SHOT))
|
||||
|
||||
// -- Post download processing
|
||||
|
||||
private fun onFinish(subject: Subject, id: Int) = when (subject) {
|
||||
is Module -> subject.onFinish(id)
|
||||
is Manager -> subject.onFinish(id)
|
||||
}
|
||||
|
||||
private fun Module.onFinish(id: Int) = when (action) {
|
||||
Flash -> {
|
||||
UiThreadHandler.run {
|
||||
(ForegroundTracker.foreground as? BaseUIActivity<*, *>)
|
||||
?.navigation?.navigate(FlashFragment.install(file, id))
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
private fun Manager.onFinish(id: Int) {
|
||||
remove(id)
|
||||
APKInstall.install(context, file.toFile())
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SUBJECT_KEY = "download_subject"
|
||||
private const val REQUEST_CODE = 1
|
||||
|
||||
private val progressBroadcast = MutableLiveData<Pair<Float, Subject>?>()
|
||||
|
||||
@ -233,13 +182,13 @@ class DownloadService : BaseService() {
|
||||
context.intent<DownloadService>().putExtra(SUBJECT_KEY, subject)
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
fun pendingIntent(context: Context, subject: Subject): PendingIntent {
|
||||
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 >= 26) {
|
||||
PendingIntent.getForegroundService(context, nextInt(), intent(context, subject),
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
getForegroundService(context, REQUEST_CODE, intent, flag)
|
||||
} else {
|
||||
PendingIntent.getService(context, nextInt(), intent(context, subject),
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
getService(context, REQUEST_CODE, intent, flag)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.topjohnwu.magisk.core.download
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.net.toFile
|
||||
import com.topjohnwu.magisk.DynAPK
|
||||
import com.topjohnwu.magisk.R
|
||||
@ -8,29 +7,14 @@ import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
import com.topjohnwu.magisk.core.tasks.HideAPK
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.ktx.copyAndClose
|
||||
import com.topjohnwu.magisk.ktx.relaunchApp
|
||||
import com.topjohnwu.magisk.ktx.withStreams
|
||||
import com.topjohnwu.magisk.ktx.writeTo
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
private fun Context.patch(apk: File) {
|
||||
val patched = File(apk.parent, "patched.apk")
|
||||
HideAPK.patch(this, apk, patched, packageName, applicationInfo.nonLocalizedLabel)
|
||||
apk.delete()
|
||||
patched.renameTo(apk)
|
||||
}
|
||||
|
||||
private fun DownloadService.notifyHide(id: Int) {
|
||||
update(id) {
|
||||
it.setProgress(0, 0, true)
|
||||
.setContentTitle(getString(R.string.hide_app_title))
|
||||
.setContentText("")
|
||||
}
|
||||
}
|
||||
|
||||
private class DupOutputStream(
|
||||
private class TeeOutputStream(
|
||||
private val o1: OutputStream,
|
||||
private val o2: OutputStream
|
||||
) : OutputStream() {
|
||||
@ -50,20 +34,26 @@ private class DupOutputStream(
|
||||
|
||||
suspend fun DownloadService.handleAPK(subject: Subject.Manager, stream: InputStream) {
|
||||
fun write(output: OutputStream) {
|
||||
val ext = subject.externalFile.outputStream()
|
||||
val o = DupOutputStream(ext, output)
|
||||
withStreams(stream, o) { src, out -> src.copyTo(out) }
|
||||
val external = subject.externalFile.outputStream()
|
||||
stream.copyAndClose(TeeOutputStream(external, output))
|
||||
}
|
||||
|
||||
if (isRunningAsStub) {
|
||||
val apk = subject.file.toFile()
|
||||
val id = subject.notifyID()
|
||||
val id = subject.notifyId
|
||||
write(DynAPK.update(this).outputStream())
|
||||
if (Info.stub!!.version < subject.stub.versionCode) {
|
||||
// Also upgrade stub
|
||||
notifyHide(id)
|
||||
update(id) {
|
||||
it.setProgress(0, 0, true)
|
||||
.setContentTitle(getString(R.string.hide_app_title))
|
||||
.setContentText("")
|
||||
}
|
||||
service.fetchFile(subject.stub.link).byteStream().writeTo(apk)
|
||||
patch(apk)
|
||||
val patched = File(apk.parent, "patched.apk")
|
||||
HideAPK.patch(this, apk, patched, packageName, applicationInfo.nonLocalizedLabel)
|
||||
apk.delete()
|
||||
patched.renameTo(apk)
|
||||
} else {
|
||||
// Simply relaunch the app
|
||||
stopSelf()
|
||||
|
@ -1,43 +0,0 @@
|
||||
package com.topjohnwu.magisk.core.download
|
||||
|
||||
import android.net.Uri
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.ktx.forEach
|
||||
import com.topjohnwu.magisk.ktx.withStreams
|
||||
import java.io.InputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
fun InputStream.toModule(file: Uri, 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
|
||||
zin.forEach { entry ->
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,12 @@
|
||||
package com.topjohnwu.magisk.core.download
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Parcelable
|
||||
import androidx.core.net.toFile
|
||||
import androidx.core.net.toUri
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.model.MagiskJson
|
||||
@ -10,22 +15,33 @@ import com.topjohnwu.magisk.core.model.module.OnlineModule
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
import com.topjohnwu.magisk.ktx.cachedFile
|
||||
import com.topjohnwu.magisk.ui.flash.FlashFragment
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
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()
|
||||
|
||||
enum class Action {
|
||||
Flash,
|
||||
Download
|
||||
}
|
||||
|
||||
sealed class Subject : Parcelable {
|
||||
|
||||
abstract val url: String
|
||||
abstract val file: Uri
|
||||
abstract val action: Action
|
||||
abstract val title: String
|
||||
abstract val notifyId: Int
|
||||
|
||||
abstract fun pendingIntent(context: Context): PendingIntent
|
||||
|
||||
@Parcelize
|
||||
class Module(
|
||||
val module: OnlineModule,
|
||||
override val action: Action
|
||||
val action: Action,
|
||||
override val notifyId: Int = Notifications.nextId()
|
||||
) : Subject() {
|
||||
override val url: String get() = module.zip_url
|
||||
override val title: String get() = module.downloadFilename
|
||||
@ -34,14 +50,19 @@ sealed class Subject : Parcelable {
|
||||
override val file by lazy {
|
||||
MediaStoreUtils.getFile(title).uri
|
||||
}
|
||||
|
||||
override fun pendingIntent(context: Context) = when (action) {
|
||||
Action.Flash -> FlashFragment.installIntent(context, file)
|
||||
else -> Intent().toPending(context)
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
class Manager(
|
||||
private val json: MagiskJson = Info.remote.magisk,
|
||||
val stub: StubJson = Info.remote.stub
|
||||
val stub: StubJson = Info.remote.stub,
|
||||
override val notifyId: Int = Notifications.nextId()
|
||||
) : Subject() {
|
||||
override val action get() = Action.Download
|
||||
override val title: String get() = "Magisk-${json.version}(${json.versionCode})"
|
||||
override val url: String get() = json.link
|
||||
|
||||
@ -51,15 +72,14 @@ sealed class Subject : Parcelable {
|
||||
}
|
||||
|
||||
val externalFile get() = MediaStoreUtils.getFile("$title.apk").uri
|
||||
|
||||
override fun pendingIntent(context: Context) =
|
||||
APKInstall.installIntent(context, file.toFile()).toPending(context)
|
||||
}
|
||||
|
||||
fun notifyID() = hashCode()
|
||||
}
|
||||
|
||||
sealed class Action : Parcelable {
|
||||
@Parcelize
|
||||
object Flash : Action()
|
||||
|
||||
@Parcelize
|
||||
object Download : Action()
|
||||
@SuppressLint("InlinedApi")
|
||||
protected fun Intent.toPending(context: Context): PendingIntent {
|
||||
return PendingIntent.getActivity(context, notifyId, this,
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_ONE_SHOT)
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import java.io.InputStream
|
||||
|
||||
class ProgressInputStream(
|
||||
base: InputStream,
|
||||
val progressEmitter: (Long) -> Unit = {}
|
||||
val progressEmitter: (Long) -> Unit
|
||||
) : FilterInputStream(base) {
|
||||
|
||||
private var bytesRead = 0L
|
||||
@ -40,4 +40,9 @@ class ProgressInputStream(
|
||||
}
|
||||
return sz
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
super.close()
|
||||
progressEmitter(bytesRead)
|
||||
}
|
||||
}
|
||||
|
@ -14,8 +14,8 @@ class ModuleInstallDialog(private val item: OnlineModule) : DialogEvent() {
|
||||
with(dialog) {
|
||||
|
||||
fun download(install: Boolean) {
|
||||
val config = if (install) Action.Flash else Action.Download
|
||||
val subject = Subject.Module(item, config)
|
||||
val action = if (install) Action.Flash else Action.Download
|
||||
val subject = Subject.Module(item, action)
|
||||
DownloadService.start(context, subject)
|
||||
}
|
||||
|
||||
|
@ -19,9 +19,6 @@ inline fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
fun InputStream.writeTo(file: File) =
|
||||
withStreams(this, file.outputStream()) { reader, writer -> reader.copyTo(writer) }
|
||||
|
||||
inline fun <In : InputStream, Out : OutputStream> withStreams(
|
||||
inStream: In,
|
||||
outStream: Out,
|
||||
@ -34,18 +31,19 @@ inline fun <In : InputStream, Out : OutputStream> withStreams(
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> MutableList<T>.update(newList: List<T>) {
|
||||
clear()
|
||||
addAll(newList)
|
||||
}
|
||||
fun InputStream.copyAndClose(out: OutputStream) = withStreams(this, out) { i, o -> i.copyTo(o) }
|
||||
|
||||
fun InputStream.writeTo(file: File) = copyAndClose(file.outputStream())
|
||||
|
||||
operator fun <E> SparseArrayCompat<E>.set(key: Int, value: E) {
|
||||
put(key, value)
|
||||
}
|
||||
|
||||
fun <T> MutableList<T>.synchronized() = Collections.synchronizedList(this)
|
||||
fun <T> MutableSet<T>.synchronized() = Collections.synchronizedSet(this)
|
||||
fun <K, V> MutableMap<K, V>.synchronized() = Collections.synchronizedMap(this)
|
||||
fun <T> MutableList<T>.synchronized(): MutableList<T> = Collections.synchronizedList(this)
|
||||
|
||||
fun <T> MutableSet<T>.synchronized(): MutableSet<T> = Collections.synchronizedSet(this)
|
||||
|
||||
fun <K, V> MutableMap<K, V>.synchronized(): MutableMap<K, V> = Collections.synchronizedMap(this)
|
||||
|
||||
fun SimpleDateFormat.parseOrNull(date: String) =
|
||||
runCatching { parse(date) }.onFailure { Timber.e(it) }.getOrNull()
|
||||
|
@ -111,16 +111,14 @@ class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>()
|
||||
|
||||
/* Installing is understood as flashing modules / zips */
|
||||
|
||||
fun installIntent(context: Context, file: Uri, id: Int = -1) = FlashFragmentArgs(
|
||||
fun installIntent(context: Context, file: Uri) = FlashFragmentArgs(
|
||||
action = Const.Value.FLASH_ZIP,
|
||||
additionalData = file,
|
||||
dismissId = id
|
||||
).let { createIntent(context, it) }
|
||||
|
||||
fun install(file: Uri, id: Int) = MainDirections.actionFlashFragment(
|
||||
fun install(file: Uri) = MainDirections.actionFlashFragment(
|
||||
action = Const.Value.FLASH_ZIP,
|
||||
additionalData = file,
|
||||
dismissId = id
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,6 @@ import com.topjohnwu.magisk.databinding.itemBindingOf
|
||||
import com.topjohnwu.magisk.databinding.set
|
||||
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||
import com.topjohnwu.magisk.ktx.*
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import com.topjohnwu.superuser.CallbackList
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -50,9 +49,7 @@ class FlashViewModel : BaseViewModel() {
|
||||
}
|
||||
|
||||
fun startFlashing() {
|
||||
val (action, uri, id) = args
|
||||
if (id != -1)
|
||||
Notifications.mgr.cancel(id)
|
||||
val (action, uri) = args
|
||||
|
||||
viewModelScope.launch {
|
||||
val result = when (action) {
|
||||
|
@ -70,4 +70,8 @@ class HomeFragment : BaseUIFragment<HomeViewModel, FragmentHomeMd2Binding>() {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
viewModel.stateManagerProgress = 0
|
||||
}
|
||||
}
|
||||
|
@ -8,19 +8,22 @@ import android.os.Build.VERSION.SDK_INT
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.graphics.drawable.toIcon
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Const.ID.PROGRESS_NOTIFICATION_CHANNEL
|
||||
import com.topjohnwu.magisk.core.Const.ID.UPDATE_NOTIFICATION_CHANNEL
|
||||
import com.topjohnwu.magisk.core.download.DownloadService
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
import com.topjohnwu.magisk.ktx.getBitmap
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
object Notifications {
|
||||
|
||||
val mgr by lazy { AppContext.getSystemService<NotificationManager>()!! }
|
||||
|
||||
private const val APK_UPDATE_NOTIFICATION_ID = 5
|
||||
private val nextId = AtomicInteger(APK_UPDATE_NOTIFICATION_ID)
|
||||
|
||||
fun setup(context: Context) {
|
||||
if (SDK_INT >= 26) {
|
||||
var channel = NotificationChannel(UPDATE_NOTIFICATION_CHANNEL,
|
||||
@ -46,7 +49,7 @@ object Notifications {
|
||||
}
|
||||
|
||||
fun managerUpdate(context: Context) {
|
||||
val intent = DownloadService.pendingIntent(context, Subject.Manager())
|
||||
val intent = DownloadService.getPendingIntent(context, Subject.Manager())
|
||||
|
||||
val builder = updateBuilder(context)
|
||||
.setContentTitle(context.getString(R.string.magisk_update_title))
|
||||
@ -54,7 +57,7 @@ object Notifications {
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(intent)
|
||||
|
||||
mgr.notify(Const.ID.APK_UPDATE_NOTIFICATION_ID, builder.build())
|
||||
mgr.notify(APK_UPDATE_NOTIFICATION_ID, builder.build())
|
||||
}
|
||||
|
||||
fun progress(context: Context, title: CharSequence): Notification.Builder {
|
||||
@ -69,4 +72,6 @@ object Notifications {
|
||||
.setOngoing(true)
|
||||
return builder
|
||||
}
|
||||
|
||||
fun nextId() = nextId.incrementAndGet()
|
||||
}
|
||||
|
@ -51,11 +51,6 @@
|
||||
app:argType="android.net.Uri"
|
||||
app:nullable="true" />
|
||||
|
||||
<argument
|
||||
android:name="dismiss_id"
|
||||
android:defaultValue="-1"
|
||||
app:argType="integer" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
|
Loading…
x
Reference in New Issue
Block a user