Commit 6dbd8baa authored by topjohnwu's avatar topjohnwu

Cleanup DownloadService

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