diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6b1aac312..40e579466 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -61,7 +61,7 @@ diff --git a/app/src/main/java/com/topjohnwu/magisk/core/Service.kt b/app/src/main/java/com/topjohnwu/magisk/core/Service.kt new file mode 100644 index 000000000..5398c8497 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/core/Service.kt @@ -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) + } +} diff --git a/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt b/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadManager.kt similarity index 50% rename from app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt rename to app/src/main/java/com/topjohnwu/magisk/core/download/DownloadManager.kt index 591a3c457..20de8f0be 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadManager.kt @@ -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 +) { + + interface Session { + val context: Context - private val job = Job() - - override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { - intent.getParcelableExtra(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?>() + + 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() + .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() + 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().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)) } } } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/download/NotificationService.kt b/app/src/main/java/com/topjohnwu/magisk/core/download/NotificationService.kt deleted file mode 100644 index ef7c4c55d..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/core/download/NotificationService.kt +++ /dev/null @@ -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().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?>() - - private fun broadcast(progress: Float, subject: Subject) { - progressBroadcast.postValue(progress to subject) - } - } -} diff --git a/app/src/main/java/com/topjohnwu/magisk/core/download/Subject.kt b/app/src/main/java/com/topjohnwu/magisk/core/download/Subject.kt index 4c9ea4600..c0e93fe50 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/download/Subject.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/download/Subject.kt @@ -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, diff --git a/app/src/main/java/com/topjohnwu/magisk/dialog/ManagerInstallDialog.kt b/app/src/main/java/com/topjohnwu/magisk/dialog/ManagerInstallDialog.kt index f29c26ee9..7d14d7df6 100644 --- a/app/src/main/java/com/topjohnwu/magisk/dialog/ManagerInstallDialog.kt +++ b/app/src/main/java/com/topjohnwu/magisk/dialog/ManagerInstallDialog.kt @@ -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 diff --git a/app/src/main/java/com/topjohnwu/magisk/dialog/OnlineModuleInstallDialog.kt b/app/src/main/java/com/topjohnwu/magisk/dialog/OnlineModuleInstallDialog.kt index 2ed97705b..85a19f4dc 100644 --- a/app/src/main/java/com/topjohnwu/magisk/dialog/OnlineModuleInstallDialog.kt +++ b/app/src/main/java/com/topjohnwu/magisk/dialog/OnlineModuleInstallDialog.kt @@ -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, diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt index 72c52c7c7..2a7bcc6df 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt @@ -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(), MenuProvider { @@ -25,7 +25,7 @@ class HomeFragment : BaseFragment(), 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) { diff --git a/app/src/main/java/com/topjohnwu/magisk/view/Notifications.kt b/app/src/main/java/com/topjohnwu/magisk/view/Notifications.kt index 229139e6a..c3fcd7c13 100644 --- a/app/src/main/java/com/topjohnwu/magisk/view/Notifications.kt +++ b/app/src/main/java/com/topjohnwu/magisk/view/Notifications.kt @@ -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)